Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions config/openclaw/openclaw.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@
"memory-wiki": {
"enabled": true,
"config": {
"vaultMode": "unsafe-local",
"vaultMode": "bridge",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

Switching from unsafe-local to bridge mode is a strong architectural improvement. However, ensure there's a migration path if any consumers still expect unsafeLocal configuration keys. Consider documenting bridge mode prerequisites (what produces the artifacts/events) to prevent silent indexing gaps during rollout.

Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#2037
File: config/openclaw/openclaw.template.json#L1055
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
Switching from `unsafe-local` to `bridge` mode is a strong architectural improvement. However, ensure there's a migration path if any consumers still expect `unsafeLocal` configuration keys. Consider documenting bridge mode prerequisites (what produces the artifacts/events) to prevent silent indexing gaps during rollout.

"vault": {
"path": "__HOME__/ghq/github.com/shunkakinoki/wiki",
"renderMode": "obsidian"
Expand All @@ -1063,11 +1063,13 @@
"vaultName": "Shun Kakinoki",
"openAfterWrites": false
},
"unsafeLocal": {
"allowPrivateMemoryCoreAccess": true,
"paths": [
"__HOME__/.openclaw/workspace"
]
"bridge": {
"enabled": true,
"readMemoryArtifacts": true,
"indexDreamReports": true,
"indexDailyNotes": true,
"indexMemoryRoot": true,
"followMemoryEvents": true
},
"ingest": {
"autoCompile": true,
Expand Down
14 changes: 8 additions & 6 deletions config/openclaw/openclaw.tpl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@
"memory-wiki": {
"enabled": true,
"config": {
"vaultMode": "unsafe-local",
"vaultMode": "bridge",
"vault": {
"path": "__HOME__/ghq/github.com/shunkakinoki/wiki",
"renderMode": "obsidian"
Expand All @@ -1063,11 +1063,13 @@
"vaultName": "Shun Kakinoki",
"openAfterWrites": false
},
"unsafeLocal": {
"allowPrivateMemoryCoreAccess": true,
"paths": [
"__HOME__/.openclaw/workspace"
]
"bridge": {
"enabled": true,
"readMemoryArtifacts": true,
"indexDreamReports": true,
"indexDailyNotes": true,
"indexMemoryRoot": true,
"followMemoryEvents": true
},
"ingest": {
"autoCompile": true,
Expand Down
20 changes: 10 additions & 10 deletions home-manager/services/obsidian/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ let
)
);

# Trigger obsidian-git's auto-backup via CDP. The Electron renderer's
# setTimeout doesn't fire under headless xvfb (futex blocks the event
# loop), but CDP uses IPC and bypasses it. All git operations still
# run through the obsidian-git plugin.
obsidianGitTrigger = pkgs.writeShellScriptBin "obsidian-git-trigger" (
# Sync the vault directly with Git. Headless Obsidian does not reliably
# load community plugins, so Git durability must not depend on its renderer.
wikiGitSync = pkgs.writeShellScriptBin "wiki-git-sync" (
builtins.readFile (
pkgs.replaceVars ./obsidian-git-trigger.sh {
inherit (pkgs) curl jq websocat;
inherit (pkgs) coreutils git;
utilLinux = pkgs.util-linux;
vaultDir = "${homeDir}/ghq/github.com/shunkakinoki/wiki";
}
)
);
Expand Down Expand Up @@ -64,19 +64,19 @@ lib.mkIf host.isKyber {

systemd.user.services.obsidian-git-trigger = {
Unit = {
Description = "Trigger obsidian-git auto-backup via CDP";
After = [ "obsidian.service" ];
Description = "Commit and push the memory wiki vault";
After = [ "network-online.target" ];
X-SwitchMethod = "keep-old";
};
Service = {
Type = "oneshot";
ExecStart = "${obsidianGitTrigger}/bin/obsidian-git-trigger";
ExecStart = "${wikiGitSync}/bin/wiki-git-sync";
};
};

systemd.user.timers.obsidian-git-trigger = {
Unit = {
Description = "Trigger obsidian-git auto-backup every 3 minutes";
Description = "Commit and push the memory wiki every 3 minutes";
};
Timer = {
OnBootSec = "2min";
Expand Down
56 changes: 36 additions & 20 deletions home-manager/services/obsidian/obsidian-git-trigger.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
#!/usr/bin/env bash
# Trigger obsidian-git's commitAndSync via CDP.
#
# The Electron renderer's setTimeout doesn't fire under headless xvfb
# (futex_wait_queue blocks the event loop pump), but CDP messages use
# IPC and bypass the stuck loop. We trigger the backup and keep the
# websocket open with ping-interval for 15s to pump the event loop
# while git operations complete through the obsidian-git plugin.

CDP="http://localhost:9222"

WS_URL=$(@curl@/bin/curl -sf "$CDP/json" | @jq@/bin/jq -r '.[0].webSocketDebuggerUrl // empty')
[ -z "$WS_URL" ] && exit 0

# Send trigger, then keep stdin open for 15s so websocat stays alive.
# The --ping-interval sends websocket pings that pump the Electron
# event loop, allowing the obsidian-git promise queue to execute.
{
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"app.plugins.plugins['"'"'obsidian-git'"'"']?.automaticsManager?.doAutoCommitAndSync()"}}'
sleep 15
} | @websocat@/bin/websocat --ping-interval 1 "$WS_URL" >/dev/null 2>&1 || true
# Commit, rebase, and push the memory wiki without depending on Obsidian's
# headless renderer or the obsidian-git community plugin.

set -euo pipefail

VAULT="@vaultDir@"
GIT="@git@/bin/git"

if ! "$GIT" -C "$VAULT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "wiki-git-sync: vault is not a Git checkout: $VAULT" >&2
exit 1
fi

BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

With set -e enabled, symbolic-ref --short HEAD will exit non-zero in a detached HEAD state, bypassing your explicit branch validation error message on line 17. Consider handling this case explicitly: BRANCH=$('$GIT' -C '$VAULT' symbolic-ref --short HEAD 2>/dev/null || echo '') and then checking for empty string with a clear "detached HEAD" error message.

Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#2037
File: home-manager/services/obsidian/obsidian-git-trigger.sh#L15
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
With `set -e` enabled, `symbolic-ref --short HEAD` will exit non-zero in a detached HEAD state, bypassing your explicit branch validation error message on line 17. Consider handling this case explicitly: `BRANCH=$('$GIT' -C '$VAULT' symbolic-ref --short HEAD 2>/dev/null || echo '')` and then checking for empty string with a clear "detached HEAD" error message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: With set -e active, git symbolic-ref --short HEAD exits non-zero in a detached HEAD state, terminating the script before the friendly error message on line 17 is reached. Consider capturing the failure explicitly:

BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD 2>/dev/null) || {
  echo "wiki-git-sync: HEAD is detached or unborn in: $VAULT" >&2
  exit 1
}

This preserves clear diagnostics for operators debugging unattended failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/obsidian/obsidian-git-trigger.sh, line 15:

<comment>With `set -e` active, `git symbolic-ref --short HEAD` exits non-zero in a detached HEAD state, terminating the script before the friendly error message on line 17 is reached. Consider capturing the failure explicitly:
```bash
BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD 2>/dev/null) || {
  echo "wiki-git-sync: HEAD is detached or unborn in: $VAULT" >&2
  exit 1
}

This preserves clear diagnostics for operators debugging unattended failures.

@@ -1,21 +1,37 @@ + exit 1 +fi + +BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD) +if [ "$BRANCH" != "main" ]; then + echo "wiki-git-sync: expected main branch, found $BRANCH" >&2 ```
Suggested change
BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD)
BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD 2>/dev/null) || {
echo "wiki-git-sync: HEAD is detached or unborn in: $VAULT" >&2
exit 1
}

if [ "$BRANCH" != "main" ]; then
echo "wiki-git-sync: expected main branch, found $BRANCH" >&2
exit 1
fi
Comment on lines +15 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the repository is in a detached HEAD state, git symbolic-ref will fail with a non-zero exit code. Because set -e is enabled, the script will terminate immediately without printing the descriptive error message. Providing a fallback or handling the failure of symbolic-ref ensures that a clear error message is always printed.

Suggested change
BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD)
if [ "$BRANCH" != "main" ]; then
echo "wiki-git-sync: expected main branch, found $BRANCH" >&2
exit 1
fi
BRANCH=$("$GIT" -C "$VAULT" symbolic-ref --short HEAD 2>/dev/null || echo "detached HEAD")
if [ "$BRANCH" != "main" ]; then
echo "wiki-git-sync: expected main branch, found $BRANCH" >&2
exit 1
fi


exec 9>"$VAULT/.git/wiki-sync.lock"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The lock file path assumes .git is a directory, but in linked worktrees and submodules .git can be a file. That causes exec 9>"$VAULT/.git/wiki-sync.lock" to fail with Not a directory, and because the script uses set -euo pipefail, the sync aborts entirely. Using git rev-parse --absolute-git-dir avoids this by always resolving the true git directory path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/obsidian/obsidian-git-trigger.sh, line 21:

<comment>The lock file path assumes `.git` is a directory, but in linked worktrees and submodules `.git` can be a file. That causes `exec 9>"$VAULT/.git/wiki-sync.lock"` to fail with `Not a directory`, and because the script uses `set -euo pipefail`, the sync aborts entirely. Using `git rev-parse --absolute-git-dir` avoids this by always resolving the true git directory path.</comment>

<file context>
@@ -1,21 +1,37 @@
+  exit 1
+fi
+
+exec 9>"$VAULT/.git/wiki-sync.lock"
+if ! @utilLinux@/bin/flock -n 9; then
+  exit 0
</file context>
Suggested change
exec 9>"$VAULT/.git/wiki-sync.lock"
GITDIR=$("$GIT" -C "$VAULT" rev-parse --absolute-git-dir)
exec 9>"$GITDIR/wiki-sync.lock"

if ! @utilLinux@/bin/flock -n 9; then
exit 0
fi
Comment on lines +21 to +24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a hardcoded path of $VAULT/.git/wiki-sync.lock assumes that .git is always a directory. However, in Git worktrees or submodules, .git is a file pointing to the actual git directory elsewhere, which would cause this redirection to fail with a Not a directory error. Using git rev-parse --absolute-git-dir is a more robust way to locate the git directory.

Suggested change
exec 9>"$VAULT/.git/wiki-sync.lock"
if ! @utilLinux@/bin/flock -n 9; then
exit 0
fi
GIT_DIR=$("$GIT" -C "$VAULT" rev-parse --absolute-git-dir)
exec 9>"$GIT_DIR/wiki-sync.lock"
if ! @utilLinux@/bin/flock -n 9; then
exit 0
fi


"$GIT" -C "$VAULT" add -A

if ! "$GIT" -C "$VAULT" diff --cached --quiet; then
"$GIT" -C "$VAULT" -c commit.gpgsign=false commit -m "vault backup: $(@coreutils@/bin/date -u '+%Y-%m-%d %H:%M:%S UTC')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

Disabling GPG signing removes commit provenance guarantees. While pragmatic for unattended commits, this weakens the security model. Consider documenting this tradeoff in comments and implementing compensating controls such as: restrictive repository access permissions, signed tags for releases, or downstream CI verification of commit integrity.

Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#2037
File: home-manager/services/obsidian/obsidian-git-trigger.sh#L29
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
Disabling GPG signing removes commit provenance guarantees. While pragmatic for unattended commits, this weakens the security model. Consider documenting this tradeoff in comments and implementing compensating controls such as: restrictive repository access permissions, signed tags for releases, or downstream CI verification of commit integrity.

fi

"$GIT" -C "$VAULT" fetch origin main
"$GIT" -C "$VAULT" rebase origin/main
Comment on lines +32 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since this script runs unattended as a systemd timer, any merge conflict during git rebase origin/main will cause the script to exit immediately (due to set -e), leaving the repository in a conflicted/dirty state. This will prevent all future automated sync runs from succeeding until manual intervention occurs. Aborting the rebase on failure ensures the repository is restored to a clean state.

Suggested change
"$GIT" -C "$VAULT" fetch origin main
"$GIT" -C "$VAULT" rebase origin/main
"$GIT" -C "$VAULT" fetch origin main
if ! "$GIT" -C "$VAULT" rebase origin/main; then
echo "wiki-git-sync: rebase failed, aborting to keep working tree clean" >&2
"$GIT" -C "$VAULT" rebase --abort
exit 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase conflict permanently wedges the sync. With set -e, a conflicting git rebase origin/main exits non-zero and leaves .git/rebase-merge/ on disk. On the next timer tick, git symbolic-ref --short HEAD at line 15 fails with fatal: ref HEAD is not a symbolic ref (HEAD is detached during a rebase) and the whole script bails before it can commit, rebase, or push — so unattended backups silently stop until someone runs git rebase --abort by hand. Reproduced locally by rebasing two divergent edits to the same file: symbolic-ref --short HEAD returned exit 128.

Since this rewrite is specifically about unattended durability, please self-heal here. Options:

  • After the flock is held, detect and abort a leftover rebase: if [ -d "$VAULT/.git/rebase-merge" ] || [ -d "$VAULT/.git/rebase-apply" ]; then "$GIT" -C "$VAULT" rebase --abort || true; fi
  • Run the rebase with --autostash and explicitly rebase --abort on failure so the tree is always left in a clean state.
  • Alternatively, use git pull --rebase --autostash and fail loudly (journal + non-zero exit) when it can't converge.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

A rebase conflict will leave the repository in .git/rebase-* state, causing persistent failures on subsequent timer runs until manual recovery. Consider adding explicit conflict detection (check rebase exit code) with clear logging/notification, or implement a recovery strategy (e.g., abort and merge instead) to keep unattended operation maintainable.

Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#2037
File: home-manager/services/obsidian/obsidian-git-trigger.sh#L33
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
A rebase conflict will leave the repository in `.git/rebase-*` state, causing persistent failures on subsequent timer runs until manual recovery. Consider adding explicit conflict detection (check rebase exit code) with clear logging/notification, or implement a recovery strategy (e.g., abort and merge instead) to keep unattended operation maintainable.

Comment on lines +32 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Rebase failure leaves vault stuck in mid-rebase state.

If git rebase origin/main hits conflicts, set -e exits the script but the vault remains in a rebase-in-progress state. Subsequent timer runs fail at line 15 (symbolic-ref --short HEAD) because HEAD is detached during rebase — producing an opaque git error rather than the custom branch-check message. The vault stays broken until someone manually runs git rebase --abort.

Aborting the rebase on failure keeps the vault recoverable and makes the next run retry cleanly.

🔒 Proposed fix: abort rebase on failure
 "$GIT" -C "$VAULT" fetch origin main
-"$GIT" -C "$VAULT" rebase origin/main
+"$GIT" -C "$VAULT" rebase origin/main || {
+  "$GIT" -C "$VAULT" rebase --abort
+  echo "wiki-git-sync: rebase failed, conflicts aborted" >&2
+  exit 1
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"$GIT" -C "$VAULT" fetch origin main
"$GIT" -C "$VAULT" rebase origin/main
"$GIT" -C "$VAULT" fetch origin main
"$GIT" -C "$VAULT" rebase origin/main || {
"$GIT" -C "$VAULT" rebase --abort
echo "wiki-git-sync: rebase failed, conflicts aborted" >&2
exit 1
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@home-manager/services/obsidian/obsidian-git-trigger.sh` around lines 32 - 33,
Ensure the rebase operation in obsidian-git-trigger.sh is aborted when it fails,
so conflicts do not leave the vault in an in-progress state. Update the command
sequence around `git rebase origin/main` to invoke `git rebase --abort` on
failure before exiting, while preserving the existing error handling and retry
behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A rebase conflict will exit non-zero (due to set -e) and leave .git/rebase-merge/ on disk. On the next timer tick, symbolic-ref --short HEAD fails because HEAD is detached mid-rebase, permanently wedging the unattended sync until someone runs git rebase --abort by hand.

Since this script's purpose is unattended durability, consider aborting the rebase on failure so the repo is always left in a clean state:

if ! "$GIT" -C "$VAULT" rebase origin/main; then
  echo "wiki-git-sync: rebase conflict, aborting to restore clean state" >&2
  "$GIT" -C "$VAULT" rebase --abort || true
  exit 1
fi

Optionally, detect and clean up a leftover rebase state at script start as well.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/obsidian/obsidian-git-trigger.sh, line 33:

<comment>A rebase conflict will exit non-zero (due to `set -e`) and leave `.git/rebase-merge/` on disk. On the next timer tick, `symbolic-ref --short HEAD` fails because HEAD is detached mid-rebase, permanently wedging the unattended sync until someone runs `git rebase --abort` by hand.

Since this script's purpose is unattended durability, consider aborting the rebase on failure so the repo is always left in a clean state:
```bash
if ! "$GIT" -C "$VAULT" rebase origin/main; then
  echo "wiki-git-sync: rebase conflict, aborting to restore clean state" >&2
  "$GIT" -C "$VAULT" rebase --abort || true
  exit 1
fi

Optionally, detect and clean up a leftover rebase state at script start as well.

@@ -1,21 +1,37 @@ +fi + +"$GIT" -C "$VAULT" fetch origin main +"$GIT" -C "$VAULT" rebase origin/main + +if [ "$("$GIT" -C "$VAULT" rev-parse HEAD)" != "$("$GIT" -C "$VAULT" rev-parse origin/main)" ]; then ```
Suggested change
"$GIT" -C "$VAULT" rebase origin/main
if ! "$GIT" -C "$VAULT" rebase origin/main; then
echo "wiki-git-sync: rebase conflict, aborting to restore clean state" >&2
"$GIT" -C "$VAULT" rebase --abort || true
exit 1
fi


if [ "$("$GIT" -C "$VAULT" rev-parse HEAD)" != "$("$GIT" -C "$VAULT" rev-parse origin/main)" ]; then
"$GIT" -C "$VAULT" push origin HEAD:main
fi
39 changes: 22 additions & 17 deletions spec/obsidian_git_trigger_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,42 @@ The output should include '#!/usr/bin/env bash'
End

It 'passes bash syntax check after replacing placeholders'
When run bash -c "sed -e 's|@curl@|/usr|g' -e 's|@jq@|/usr|g' -e 's|@websocat@|/usr|g' '$SCRIPT' | bash -n"
When run bash -c "sed -e 's|@coreutils@|/usr|g' -e 's|@git@|/usr|g' -e 's|@utilLinux@|/usr|g' -e 's|@vaultDir@|/tmp/wiki|g' '$SCRIPT' | bash -n"
The status should be success
End
End

Describe 'placeholder references'
It 'references curl via placeholder'
When run bash -c "grep '@curl@' '$SCRIPT'"
The output should include '@curl@'
It 'references git via placeholder'
When run bash -c "grep '@git@' '$SCRIPT'"
The output should include '@git@'
End

It 'references jq via placeholder'
When run bash -c "grep '@jq@' '$SCRIPT'"
The output should include '@jq@'
It 'references flock via placeholder'
When run bash -c "grep '@utilLinux@' '$SCRIPT'"
The output should include '@utilLinux@'
End
End

It 'references websocat via placeholder'
When run bash -c "grep '@websocat@' '$SCRIPT'"
The output should include '@websocat@'
Describe 'Git synchronization'
It 'serializes overlapping timer runs'
When run bash -c "grep 'flock -n' '$SCRIPT'"
The output should include 'flock -n'
End

It 'rebases before pushing main'
When run bash -c "grep 'rebase origin/main' '$SCRIPT'"
The output should include 'rebase origin/main'
End

Describe 'CDP integration'
It 'connects to CDP on port 9222'
When run bash -c "grep '9222' '$SCRIPT'"
The output should include '9222'
It 'disables interactive signing for unattended commits'
When run bash -c "grep 'commit.gpgsign=false' '$SCRIPT'"
The output should include 'commit.gpgsign=false'
End

It 'triggers doAutoCommitAndSync'
When run bash -c "grep 'doAutoCommitAndSync' '$SCRIPT'"
The output should include 'doAutoCommitAndSync'
It 'fails when the vault is not a Git checkout'
When run bash -c "grep 'vault is not a Git checkout' '$SCRIPT'"
The output should include 'vault is not a Git checkout'
End
End

Expand Down
Loading