fix(agents-vault): always link the current project's memory

The guard around the symlink step only linked when the live Claude
project directory already existed, which is exactly backwards for the
clone-onto-a-new-machine restore case: a freshly cloned vault entry
would be silently left unlinked and a starting agent would write fresh,
history-less memory instead. _agents_repo_ensure_symlink already makes
its own parent directories and is idempotent, so nothing depended on
the guard; it is removed and the link is now attempted unconditionally.

Also stop swallowing a refused or failed link as success: the helper's
exit status is now checked, and agents-vault reports its own error and
exits 1 instead of silently continuing with no link in place.

Smaller fixes from the same review pass:
- check the exit status of _agents_repo_install_tools and the
  core.hooksPath git config write, instead of discarding both
- give the vmem mkdir failure a stderr message like every other fatal
  in the function
- guard hostname with type -q and add it to DEPENDENCIES
- .version creation now sets changed, so --link (which skips the
  commit step) reports it in --quiet mode
- reword --link's help/doc text: it still scaffolds the vault and
  links memory, it only skips the final commit
- drop the unused c_dim color variable
- move the __fish_agent_vault_dir / __fish_agent_vault_autopush
  documentation below Opinionated Components so its NOTE: callout
  (now flush-left so it actually renders as a Starlight Aside, per
  review) doesn't become the first Note aside in the page and steal
  the existing test's assertions about the original 4-bullet one

Adds two tests: pre-seeded vault entry with no live directory at all
(the restore path the guard was breaking), and a forced link failure
asserting agents-vault now exits 1 instead of 0.
This commit is contained in:
2026-09-03 18:56:45 -04:00
parent 2c185f7e23
commit 19126316a7
3 changed files with 128 additions and 32 deletions
+19 -19
View File
@@ -54,25 +54,6 @@ Example: to increase the scrollback history limit:
set -gx SCROLLBACK_HISTORY_MAX_FILES 200 set -gx SCROLLBACK_HISTORY_MAX_FILES 200
## Agent Memory Vault
__fish_agent_vault_dir
Overrides the agent memory vault location. Defaults to
$XDG_DATA_HOME/agent-vault (or ~/.local/share/agent-vault).
__fish_agent_vault_autopush
When set to 1, agents-vault also pushes on wrapper launch. Defaults to
off: the vault commits locally on every launch and pushes from the
Claude Code SessionEnd hook or an explicit agents-vault --push.
NOTE:
With autopush off and no SessionEnd hook installed, backups accumulate
locally and never reach the remote. Run agents-vault --status to check
how far ahead the vault is.
## Fish Universal Variables ## Fish Universal Variables
Some settings (fzf colors, theme) are stored in fish_variables via Some settings (fzf colors, theme) are stored in fish_variables via
@@ -178,6 +159,25 @@ interactively. See [Components Reference](/08-components-reference/) for the
full sub-category breakdown of every category. full sub-category breakdown of every category.
## Agent Memory Vault
__fish_agent_vault_dir
Overrides the agent memory vault location. Defaults to
$XDG_DATA_HOME/agent-vault (or ~/.local/share/agent-vault).
__fish_agent_vault_autopush
When set to 1, agents-vault also pushes on wrapper launch. Defaults to
off: the vault commits locally on every launch and pushes from the
Claude Code SessionEnd hook or an explicit agents-vault --push.
NOTE:
With autopush off and no SessionEnd hook installed, backups accumulate
locally and never reach the remote. Run agents-vault --status to check
how far ahead the vault is.
## Prompt and Theme ## Prompt and Theme
### Starship ### Starship
+42 -13
View File
@@ -6,7 +6,7 @@
# #
# DEPENDENCIES # DEPENDENCIES
# _agents_vault_dir, _agents_repo_slug, _agents_repo_ensure_symlink, # _agents_vault_dir, _agents_repo_slug, _agents_repo_ensure_symlink,
# _agents_repo_sync, _agents_repo_install_tools, git # _agents_repo_sync, _agents_repo_install_tools, git, hostname
# #
# SYNOPSIS # SYNOPSIS
# agents-vault [--link] [--push] [--restore] [--status] # agents-vault [--link] [--push] [--restore] [--status]
@@ -36,7 +36,8 @@
# never denylisted, so nothing new upstream adds can leak in. # never denylisted, so nothing new upstream adds can leak in.
# #
# ARGUMENTS # ARGUMENTS
# --link Ensure this project's memory link only; do not commit # --link Scaffold the vault and link this project's memory; skip
# the final commit
# --push Commit and push to the vault remote # --push Commit and push to the vault remote
# --restore Walk the vault, relink what is possible, report the rest # --restore Walk the vault, relink what is possible, report the rest
# --status Show entries, link health, remote state, and orphans # --status Show entries, link health, remote state, and orphans
@@ -68,7 +69,6 @@ function agents-vault --description 'track curated agent memory in a host-scoped
set -l c_flag (set_color yellow) set -l c_flag (set_color yellow)
set -l c_ok (set_color green) set -l c_ok (set_color green)
set -l c_warn (set_color yellow) set -l c_warn (set_color yellow)
set -l c_dim (set_color brblack)
set -l c_err (set_color red) set -l c_err (set_color red)
set -l c_reset (set_color normal) set -l c_reset (set_color normal)
@@ -83,7 +83,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
echo echo
echo "$c_head""Options:$c_reset" echo "$c_head""Options:$c_reset"
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message" echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
echo " $c_flag--link$c_reset Ensure this project's memory link only" echo " $c_flag--link$c_reset Scaffold + link this project; skip the commit"
echo " $c_flag--push$c_reset Commit and push to the vault remote" echo " $c_flag--push$c_reset Commit and push to the vault remote"
echo " $c_flag--restore$c_reset Relink everything possible, report the rest" echo " $c_flag--restore$c_reset Relink everything possible, report the rest"
echo " $c_flag--status$c_reset Show entries, link health, remote, orphans" echo " $c_flag--status$c_reset Show entries, link health, remote, orphans"
@@ -133,7 +133,10 @@ function agents-vault --description 'track curated agent memory in a host-scoped
test $verbose -eq 1; and echo "$c_ok→ Initialized vault repo at $vault$c_reset" test $verbose -eq 1; and echo "$c_ok→ Initialized vault repo at $vault$c_reset"
end end
test -f "$vault/.version"; or echo 1.0.0 >"$vault/.version" if not test -f "$vault/.version"
echo 1.0.0 >"$vault/.version"
set changed 1
end
if not test -f "$vault/.gitignore" if not test -f "$vault/.gitignore"
printf '%s\n' \ printf '%s\n' \
@@ -165,6 +168,10 @@ function agents-vault --description 'track curated agent memory in a host-scoped
end end
set -l tools_msg (_agents_repo_install_tools "$vault") set -l tools_msg (_agents_repo_install_tools "$vault")
or begin
echo "$c_err""agents-vault: could not install .agents-tools/ into $vault$c_reset" >&2
return 1
end
if test -n "$tools_msg" if test -n "$tools_msg"
set changed 1 set changed 1
test $verbose -eq 1; and echo "$c_ok$tools_msg$c_reset" test $verbose -eq 1; and echo "$c_ok$tools_msg$c_reset"
@@ -173,6 +180,10 @@ function agents-vault --description 'track curated agent memory in a host-scoped
set -l hp (git -C "$vault" config --local core.hooksPath 2>/dev/null) set -l hp (git -C "$vault" config --local core.hooksPath 2>/dev/null)
if test "$hp" != .agents-tools/hooks if test "$hp" != .agents-tools/hooks
git -C "$vault" config --local core.hooksPath .agents-tools/hooks git -C "$vault" config --local core.hooksPath .agents-tools/hooks
or begin
echo "$c_err""agents-vault: could not set core.hooksPath in $vault$c_reset" >&2
return 1
end
set changed 1 set changed 1
end end
@@ -192,7 +203,10 @@ function agents-vault --description 'track curated agent memory in a host-scoped
set -l vmem "$entry/claude/memory" set -l vmem "$entry/claude/memory"
if not test -d "$vmem" if not test -d "$vmem"
mkdir -p "$vmem"; or return 1 if not mkdir -p "$vmem"
echo "$c_err""agents-vault: could not create $vmem$c_reset" >&2
return 1
end
set changed 1 set changed 1
end end
@@ -201,19 +215,34 @@ function agents-vault --description 'track curated agent memory in a host-scoped
set -l mangled (string replace -a '/' '-' -- "$root" | string replace -a '.' '-') set -l mangled (string replace -a '/' '-' -- "$root" | string replace -a '.' '-')
set -l live "$claude_root/$mangled/memory" set -l live "$claude_root/$mangled/memory"
if test -d "$claude_root/$mangled"; or test -d "$live" # Unconditional: this is the emergent-restore path. On a freshly
set -l link_msg (_agents_repo_ensure_symlink "$live" "$vmem") # cloned vault, $vmem already exists (populated from the clone) and
if test -n "$link_msg" # $live does not exist yet -- skipping the link here would silently
set changed 1 # leave the clone's memory unlinked and let a starting agent write
test $verbose -eq 1; and echo "$c_ok$link_msg$c_reset" # fresh, history-less memory instead. _agents_repo_ensure_symlink is
end # idempotent and makes its own parent directories, so there is
# nothing this guard would protect that the helper does not already
# handle on its own.
set -l link_msg (_agents_repo_ensure_symlink "$live" "$vmem")
set -l link_rc $status
if test $link_rc -ne 0
echo "$c_err""agents-vault: could not link $live$c_reset" >&2
return 1
end
if test -n "$link_msg"
set changed 1
test $verbose -eq 1; and echo "$c_ok$link_msg$c_reset"
end end
if not test -f "$entry/origin" if not test -f "$entry/origin"
set -l url (git -C "$root" remote get-url origin 2>/dev/null) set -l url (git -C "$root" remote get-url origin 2>/dev/null)
test -n "$url"; or set url "(none)" test -n "$url"; or set url "(none)"
set -l host ""
if type -q hostname
set host (hostname 2>/dev/null)
end
printf 'remote: %s\npath: %s\nhost: %s\n' \ printf 'remote: %s\npath: %s\nhost: %s\n' \
"$url" "$root" (hostname 2>/dev/null) >"$entry/origin" "$url" "$root" "$host" >"$entry/origin"
set changed 1 set changed 1
end end
end end
+67
View File
@@ -243,6 +243,73 @@ check "second run adds no commit" $before (git -C $vroot/agent-vault rev-list --
set -e __fish_agent_vault_dir set -e __fish_agent_vault_dir
set -e __fish_agent_vault_claude_root set -e __fish_agent_vault_claude_root
# ─────────────────── emergent restore (vault -> live) ──────────────────
# The reverse of the adoption case above: a vault entry already has curated
# memory (as if cloned from a remote onto a fresh machine) but the live
# Claude project directory does not exist at all yet. agents-vault must
# still create the link so the pre-existing memory is what a starting
# agent sees, rather than silently skipping the link because there was
# nothing local to notice yet.
echo ""
echo "== agents-vault (emergent restore) =="
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2
set -g __fish_agent_vault_dir $vroot2/agent-vault
set -g __fish_agent_vault_claude_root $croot2
set -l proj2 (new_repo https://git.rootiest.dev/rootiest/agent-vault.git)
set -l pslug2 git.rootiest.dev-rootiest-agent-vault
set -l mangled2 (string replace -a '/' '-' -- $proj2 | string replace -a '.' '-')
# Pre-seed the vault entry the way a cloned vault would already have it.
# Deliberately no $croot2/$mangled2 directory at all -- not even the
# project's own entry, let alone a memory/ subdirectory -- so the fix under
# test is exercised: linking must not depend on the live side existing.
mkdir -p $vroot2/agent-vault/projects/$pslug2/claude/memory
echo "restored memory" >$vroot2/agent-vault/projects/$pslug2/claude/memory/old.md
pushd $proj2 >/dev/null
agents-vault --silent
popd >/dev/null
check "restore: live memory link created with no prior live dir" true (test -L $croot2/$mangled2/memory; and echo true; or echo false)
check "restore: pre-existing vault content readable through the link" "restored memory" (cat $croot2/$mangled2/memory/old.md 2>/dev/null)
set -e __fish_agent_vault_dir
set -e __fish_agent_vault_claude_root
# ─────────────────── a refused link is reported as failure ─────────────
# _agents_repo_ensure_symlink refuses (exit 1, no stdout) when it cannot
# make the link -- e.g. its mkdir -p of the link's parent directory fails.
# agents-vault must surface that as its own exit 1, not swallow it and
# report success because stdout happened to be empty either way.
echo ""
echo "== agents-vault (link failure surfaces as exit 1) =="
set -l vroot3 (mktemp -d); set -ga TMPDIRS $vroot3
set -l croot3 (mktemp -d); set -ga TMPDIRS $croot3
set -g __fish_agent_vault_dir $vroot3/agent-vault
set -g __fish_agent_vault_claude_root $croot3
set -l proj3 (new_repo https://git.rootiest.dev/rootiest/link-fail-test.git)
set -l mangled3 (string replace -a '/' '-' -- $proj3 | string replace -a '.' '-')
# Make the mangled project path a plain FILE. _agents_repo_ensure_symlink's
# own `mkdir -p (path dirname $link)` then fails outright (ENOTDIR), which
# is exactly the class of failure (permissions, ENOSPC, ...) this guards.
touch $croot3/$mangled3
pushd $proj3 >/dev/null
set -l rc (agents-vault --silent 2>/dev/null; echo $status)
popd >/dev/null
check "link failure: agents-vault exits 1" 1 "$rc"
check "link failure: no link was left behind" false (test -L $croot3/$mangled3/memory; and echo true; or echo false)
set -e __fish_agent_vault_dir
set -e __fish_agent_vault_claude_root
cleanup cleanup
echo "" echo ""
echo (math $TESTS_RUN - $TESTS_FAILED)"/$TESTS_RUN passed" echo (math $TESTS_RUN - $TESTS_FAILED)"/$TESTS_RUN passed"