fix(agents-vault): keep the knowledge walk and the launch push inside their bounds
The agy knowledge allowlist walked the store with `**` and copied with plain cp, so a symlink inside the store was both followed and dereferenced. The extension rule still bounded what kind of file was collected, but not whose: a link to a home directory hands over settings.json, CLAUDE.md and every cached .json in it, and those reached a commit. A link to / made the walk itself unbounded, on the path that runs before every agent launch. The tree is now walked a level at a time and nothing that is a symlink is followed or copied. Autopush had the same shape one layer out. Neither GIT_TERMINAL_PROMPT nor GIT_ASKPASS closes a socket, and git has no connect timeout to set: against a blackholed address a push took 135s with http.lowSpeedLimit and http.lowSpeedTime set as well as without them. ssh can time itself out and is now told to; the autopush pull and push are additionally capped with timeout(1). An explicit --push stays uncapped, since it is watched and has to report what a real transfer really did. Also: scaffold /.migrate-stash into .gitignore beside /.adopt-stash, which the comment already claimed was covered; and drop the live memory path during a slug migration only when it is a link. Reached from the path-derived fallback candidate it can be a real populated directory, where rm -f correctly refuses -- but said so in rm's voice, so a --silent run that had succeeded printed what read as an error.
This commit is contained in:
@@ -170,7 +170,9 @@ full sub-category breakdown of every category.
|
|||||||
|
|
||||||
When set to 1, agents-vault also pushes on wrapper launch. Defaults to
|
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
|
off: the vault commits locally on every launch and pushes from the
|
||||||
Claude Code SessionEnd hook or an explicit agents-vault --push.
|
Claude Code SessionEnd hook or an explicit agents-vault --push. That
|
||||||
|
push is synchronous, so with autopush on the pull and the push are
|
||||||
|
each capped at 20 seconds; an explicit --push is left uncapped.
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
With autopush off and no SessionEnd hook installed, backups accumulate
|
With autopush off and no SessionEnd hook installed, backups accumulate
|
||||||
|
|||||||
+95
-13
@@ -38,7 +38,9 @@
|
|||||||
# allowlist runs all the way down, not just at the top: inside agy's
|
# allowlist runs all the way down, not just at the top: inside agy's
|
||||||
# knowledge store only *.md and *.json files are copied, so a credential
|
# knowledge store only *.md and *.json files are copied, so a credential
|
||||||
# file or a conversation database appearing there is left behind by the
|
# file or a conversation database appearing there is left behind by the
|
||||||
# same rule rather than by being known about in advance.
|
# same rule rather than by being known about in advance. Symlinks found
|
||||||
|
# inside the store are neither followed nor copied, so the allowlist
|
||||||
|
# bounds whose files it collects and not merely what kind.
|
||||||
#
|
#
|
||||||
# Global state that belongs to no project is tracked as well. Claude's
|
# Global state that belongs to no project is tracked as well. Claude's
|
||||||
# global memory directory (~/.claude/memory) is symlinked into the vault
|
# global memory directory (~/.claude/memory) is symlinked into the vault
|
||||||
@@ -131,9 +133,13 @@
|
|||||||
#
|
#
|
||||||
# NOTES
|
# NOTES
|
||||||
# Set __fish_agent_vault_dir to relocate the vault. Set
|
# Set __fish_agent_vault_dir to relocate the vault. Set
|
||||||
# __fish_agent_vault_autopush to 1 to also push on wrapper launch;
|
# __fish_agent_vault_autopush to 1 to also push on wrapper launch; it
|
||||||
# it defaults to off so a backgrounded push can never hang or prompt
|
# defaults to off because that push is synchronous and so delays every
|
||||||
# invisibly underneath a starting agent.
|
# launch. With it on, the pull and the push are each capped at 20
|
||||||
|
# seconds, since git has no connect timeout of its own and an
|
||||||
|
# unreachable remote otherwise blocks for minutes. An explicit --push
|
||||||
|
# is left uncapped: it is watched, and it must report what a real
|
||||||
|
# transfer really did.
|
||||||
#
|
#
|
||||||
# --adopt rebinds an entry; it does not pin its name. The slug is
|
# --adopt rebinds an entry; it does not pin its name. The slug is
|
||||||
# re-derived from the project on every run, so the next ordinary run
|
# re-derived from the project on every run, so the next ordinary run
|
||||||
@@ -329,10 +335,12 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
|||||||
'*.db-wal' \
|
'*.db-wal' \
|
||||||
'*.db-shm' \
|
'*.db-shm' \
|
||||||
'' \
|
'' \
|
||||||
'# --adopt stashes the entry it is about to overwrite inside .git/,' \
|
'# --adopt and the slug migration each stash the entry they are' \
|
||||||
'# out of reach of `git add -A`. This covers the fallback location' \
|
'# about to overwrite inside .git/, out of reach of `git add -A`.' \
|
||||||
'# it uses when .git is not a directory.' \
|
'# These cover the fallback locations they use when .git is not a' \
|
||||||
'/.adopt-stash' >"$vault/.gitignore"
|
'# directory.' \
|
||||||
|
'/.adopt-stash' \
|
||||||
|
'/.migrate-stash' >"$vault/.gitignore"
|
||||||
set changed 1
|
set changed 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -627,7 +635,39 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
|||||||
# Fish wildcards skip dot-led names at every path component, so
|
# Fish wildcards skip dot-led names at every path component, so
|
||||||
# dotfiles and hidden subdirectories are already out; the extension
|
# dotfiles and hidden subdirectories are already out; the extension
|
||||||
# allowlist is what keeps them out on purpose rather than by luck.
|
# allowlist is what keeps them out on purpose rather than by luck.
|
||||||
set -l kfiles $knowledge/**.md $knowledge/**.json
|
#
|
||||||
|
# The tree is walked a level at a time rather than globbed with **,
|
||||||
|
# because a symlink has to stop the walk and ** has no way to say
|
||||||
|
# so. A recursive ** descends through a symlinked directory and cp
|
||||||
|
# follows a symlinked file, which between them undo the whole point
|
||||||
|
# of the allowlist twice over. A `ln -s ~ knowledge/x` puts every
|
||||||
|
# qualifying .md and .json in the home directory -- settings.json,
|
||||||
|
# CLAUDE.md, cache and status files -- into the vault and into a
|
||||||
|
# commit: the extension rule still bounds what *kind* of file goes
|
||||||
|
# in, but the store boundary that decides *whose* files they are is
|
||||||
|
# gone. Worse, `ln -s / knowledge/x` makes the walk itself
|
||||||
|
# unbounded, and this runs synchronously in front of every agent
|
||||||
|
# launch. Fish does stop a true self-referential cycle; a symlink
|
||||||
|
# to a merely enormous tree is not a cycle.
|
||||||
|
#
|
||||||
|
# So nothing that is a symlink is ever followed or copied, whether
|
||||||
|
# it names a file or a directory. The knowledge store's own root
|
||||||
|
# may still be a link -- that one is the configured location of the
|
||||||
|
# store rather than something found inside it.
|
||||||
|
set -l kfiles
|
||||||
|
set -l kdirs "$knowledge"
|
||||||
|
while set -q kdirs[1]
|
||||||
|
set -l dir $kdirs[1]
|
||||||
|
set -e kdirs[1]
|
||||||
|
for e in $dir/*
|
||||||
|
test -L "$e"; and continue
|
||||||
|
if test -d "$e"
|
||||||
|
set -a kdirs "$e"
|
||||||
|
else if string match -qr '\.(md|json)$' -- "$e"
|
||||||
|
set -a kfiles "$e"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
set -l kfailed 0
|
set -l kfailed 0
|
||||||
for f in $kfiles
|
for f in $kfiles
|
||||||
test -f "$f"; or continue
|
test -f "$f"; or continue
|
||||||
@@ -836,7 +876,18 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
|||||||
end
|
end
|
||||||
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \
|
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \
|
||||||
>>"$entry/origin"
|
>>"$entry/origin"
|
||||||
rm -f "$live"
|
# Only a link is dropped here, and only so the relink below has
|
||||||
|
# somewhere to put the new one. Usually $live is exactly that: a
|
||||||
|
# symlink at the old entry, now dangling. But the migration is
|
||||||
|
# also reachable from the path-derived fallback candidate, and
|
||||||
|
# there $live can be a real, populated directory -- someone's
|
||||||
|
# actual memory. rm -f cannot delete it, which is the right
|
||||||
|
# outcome, but it says so on stderr in rm's own voice, so a
|
||||||
|
# --silent run that succeeded printed what reads as an error.
|
||||||
|
# The directory case needs no removal anyway:
|
||||||
|
# _agents_repo_ensure_symlink copies a populated live directory
|
||||||
|
# into the vault without clobbering before it replaces it.
|
||||||
|
test -L "$live"; and rm -f "$live"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Migrated vault entry $prev_slug → $slug$c_reset"
|
test $verbose -eq 1; and echo "$c_ok→ Migrated vault entry $prev_slug → $slug$c_reset"
|
||||||
end
|
end
|
||||||
@@ -930,10 +981,41 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
|||||||
# helper, which git consults before it ever falls back to
|
# helper, which git consults before it ever falls back to
|
||||||
# prompting; they only close off the interactive last resort,
|
# prompting; they only close off the interactive last resort,
|
||||||
# which under a starting agent is indistinguishable from a hang.
|
# which under a starting agent is indistinguishable from a hang.
|
||||||
|
#
|
||||||
|
# They close the prompt, not the socket, and git has no knob
|
||||||
|
# that closes the socket either: there is no HTTP connect
|
||||||
|
# timeout in its configuration at all, and http.lowSpeedLimit /
|
||||||
|
# http.lowSpeedTime -- the usual suggestion -- only start
|
||||||
|
# counting once bytes are moving, so they expire never against
|
||||||
|
# an address that simply blackholes the SYN. Measured against
|
||||||
|
# 192.0.2.1 with both set: no return inside 30s; unset: 135s.
|
||||||
|
#
|
||||||
|
# ssh is the one transport that can time itself out, so it is
|
||||||
|
# told to, unless the user has already said how to run ssh.
|
||||||
|
# Everything else is bounded from outside with timeout(1).
|
||||||
|
set -l gitenv GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=true
|
||||||
|
set -q GIT_SSH_COMMAND
|
||||||
|
or set -a gitenv 'GIT_SSH_COMMAND=ssh -o ConnectTimeout=10'
|
||||||
|
set -l gitnet env $gitenv
|
||||||
|
|
||||||
|
# Only autopush is wrapped. An explicit --push is a thing the
|
||||||
|
# user asked for and is watching, and it must report the real
|
||||||
|
# exit status of a real transfer, so it is allowed to take as
|
||||||
|
# long as the transfer honestly takes. Autopush runs
|
||||||
|
# synchronously in front of every agent launch, which is the
|
||||||
|
# block this whole design exists to remove; there an
|
||||||
|
# unreachable remote costs a bounded 20s per operation instead
|
||||||
|
# of an open-ended wait. timeout's own 124 is a non-zero exit
|
||||||
|
# like any other, so a bounded push still reports as failed and
|
||||||
|
# the commit it could not send has already landed locally. A
|
||||||
|
# push too slow for the bound can always be run by hand.
|
||||||
|
if not set -q _flag_push; and type -q timeout
|
||||||
|
set gitnet timeout 20 $gitnet
|
||||||
|
end
|
||||||
set -l reached 1
|
set -l reached 1
|
||||||
if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||||
if not GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=true \
|
if not $gitnet git -C "$vault" pull --rebase --autostash -q \
|
||||||
git -C "$vault" pull --rebase --autostash -q >/dev/null 2>/dev/null
|
>/dev/null 2>/dev/null
|
||||||
# Two unrelated failures land here and reporting them as
|
# Two unrelated failures land here and reporting them as
|
||||||
# one sends the user hunting for a conflict that never
|
# one sends the user hunting for a conflict that never
|
||||||
# existed. A rebase that genuinely started and stopped
|
# existed. A rebase that genuinely started and stopped
|
||||||
@@ -956,7 +1038,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
|||||||
# push would only fail a second time, more confusingly, and the
|
# push would only fail a second time, more confusingly, and the
|
||||||
# pull has already said exactly what went wrong.
|
# pull has already said exactly what went wrong.
|
||||||
if test $reached -eq 1
|
if test $reached -eq 1
|
||||||
if GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=true git -C "$vault" push -q origin HEAD
|
if $gitnet git -C "$vault" push -q origin HEAD
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Pushed the vault to origin$c_reset"
|
test $verbose -eq 1; and echo "$c_ok→ Pushed the vault to origin$c_reset"
|
||||||
else
|
else
|
||||||
# The commit above did happen, so the memory is safe
|
# The commit above did happen, so the memory is safe
|
||||||
|
|||||||
@@ -563,6 +563,54 @@ set -l dirty_new_slug git.rootiest.dev-rootiest-dirty
|
|||||||
check "fallback finds sanitized local entry" dirty-precious (cat $vroot2/agent-vault/projects/$dirty_new_slug/claude/memory/keep.md)
|
check "fallback finds sanitized local entry" dirty-precious (cat $vroot2/agent-vault/projects/$dirty_new_slug/claude/memory/keep.md)
|
||||||
check "fallback old entry removed" false (test -d $vroot2/agent-vault/projects/$dirty_local_slug; and echo true; or echo false)
|
check "fallback old entry removed" false (test -d $vroot2/agent-vault/projects/$dirty_local_slug; and echo true; or echo false)
|
||||||
|
|
||||||
|
# Same fallback path, but the live memory directory is a real populated
|
||||||
|
# directory rather than a symlink -- the shape a machine ends up in when
|
||||||
|
# an agent wrote memory while the link was missing. The migration then
|
||||||
|
# tries to clear the live path out of the relink's way, and clearing a
|
||||||
|
# directory is not something it may do: that is somebody's memory, and
|
||||||
|
# _agents_repo_ensure_symlink already folds it into the vault without
|
||||||
|
# clobbering. Refusing is therefore correct, but refusing in rm's voice
|
||||||
|
# is not: a --silent run that did the right thing and returned 0 still
|
||||||
|
# printed "rm: cannot remove ...: Is a directory", which is the only
|
||||||
|
# thing the user sees and reads as a failure.
|
||||||
|
set -l real_root (mktemp -d); set -ga TMPDIRS $real_root
|
||||||
|
set -l rp "$real_root/proj"
|
||||||
|
mkdir -p "$rp"
|
||||||
|
git -C "$rp" init -q
|
||||||
|
git -C "$rp" config user.email t@t
|
||||||
|
git -C "$rp" config user.name t
|
||||||
|
git -C "$rp" config commit.gpgsign false
|
||||||
|
git -C "$rp" config core.hooksPath /dev/null
|
||||||
|
|
||||||
|
set -l rmangled (string replace -a '/' '-' -- $rp | string replace -a '.' '-')
|
||||||
|
mkdir -p $croot2/$rmangled/memory
|
||||||
|
echo "banked" >$croot2/$rmangled/memory/old.md
|
||||||
|
|
||||||
|
pushd $rp >/dev/null
|
||||||
|
agents-vault --silent
|
||||||
|
set -l real_local_slug (_agents_repo_slug $rp)
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
# Replace the link with a real directory holding memory the vault has
|
||||||
|
# never seen, then change the slug so the migration runs.
|
||||||
|
rm -f $croot2/$rmangled/memory
|
||||||
|
mkdir -p $croot2/$rmangled/memory
|
||||||
|
echo "written-live" >$croot2/$rmangled/memory/fresh.md
|
||||||
|
git -C $rp remote add origin https://git.rootiest.dev/rootiest/realdir.git
|
||||||
|
|
||||||
|
set -l rerr (mktemp); set -ga TMPDIRS $rerr
|
||||||
|
pushd $rp >/dev/null
|
||||||
|
set -l real_rc (agents-vault --silent 2>$rerr; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
set -l real_err (cat $rerr)
|
||||||
|
set -l real_new_slug git.rootiest.dev-rootiest-realdir
|
||||||
|
check "real-directory migration succeeds" 0 "$real_rc"
|
||||||
|
check "real-directory migration stays silent" "" "$real_err"
|
||||||
|
check "real-directory migration keeps banked memory" banked (cat $vroot2/agent-vault/projects/$real_new_slug/claude/memory/old.md 2>/dev/null)
|
||||||
|
check "real-directory migration keeps live memory" written-live (cat $vroot2/agent-vault/projects/$real_new_slug/claude/memory/fresh.md 2>/dev/null)
|
||||||
|
check "real-directory migration relinks" true (test -L $croot2/$rmangled/memory; and echo true; or echo false)
|
||||||
|
check "real-directory old entry removed" false (test -d $vroot2/agent-vault/projects/$real_local_slug; and echo true; or echo false)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -699,6 +747,30 @@ printf 'transcript\n' >$agy5/knowledge/session.jsonl
|
|||||||
: >$agy5/knowledge/conversations.db-wal
|
: >$agy5/knowledge/conversations.db-wal
|
||||||
: >$agy5/knowledge/conversations.db-shm
|
: >$agy5/knowledge/conversations.db-shm
|
||||||
|
|
||||||
|
# Symlinks inside the store. The extension allowlist bounds what *kind* of
|
||||||
|
# file is collected; it says nothing about whose. A store-relative walk
|
||||||
|
# that dereferenced links would pull qualifying .md and .json out of
|
||||||
|
# whatever the link names -- a real home directory is full of them -- so
|
||||||
|
# the store boundary has to hold on its own. $outside5 stands in for that
|
||||||
|
# home: a directory the store has no business reaching into, planted with
|
||||||
|
# exactly the shapes that qualify.
|
||||||
|
set -l outside5 (mktemp -d); set -ga TMPDIRS $outside5
|
||||||
|
mkdir -p $outside5/nested
|
||||||
|
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json
|
||||||
|
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md
|
||||||
|
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/nested/deep.md
|
||||||
|
ln -s $outside5 $agy5/knowledge/linked
|
||||||
|
ln -s $outside5/target.md $agy5/knowledge/alias.md
|
||||||
|
# Not a cycle, so a recursive glob's cycle guard does not catch it: a link
|
||||||
|
# to the filesystem root simply makes the walk enormous. This one is here
|
||||||
|
# for the clock as much as for the contents -- the copy runs synchronously
|
||||||
|
# in front of every agent launch, and a `**` glob over this fixture did
|
||||||
|
# not return within 20s.
|
||||||
|
ln -s / $agy5/knowledge/root
|
||||||
|
# A genuine cycle too, since the walk must not depend on the glob's guard.
|
||||||
|
mkdir -p $agy5/knowledge/cyc
|
||||||
|
ln -s $agy5/knowledge $agy5/knowledge/cyc/loop
|
||||||
|
|
||||||
# A global (non-per-project) Claude memory directory with a sentinel file.
|
# A global (non-per-project) Claude memory directory with a sentinel file.
|
||||||
# __fish_agent_vault_claude_home is what keeps this off the real ~/.claude:
|
# __fish_agent_vault_claude_home is what keeps this off the real ~/.claude:
|
||||||
# if agents-vault ignored the override, these checks would fail here *and*
|
# if agents-vault ignored the override, these checks would fail here *and*
|
||||||
@@ -707,9 +779,11 @@ mkdir -p $chome5/memory
|
|||||||
echo global-memory >$chome5/memory/g.md
|
echo global-memory >$chome5/memory/g.md
|
||||||
|
|
||||||
set -l gp (new_repo https://git.rootiest.dev/rootiest/globals.git)
|
set -l gp (new_repo https://git.rootiest.dev/rootiest/globals.git)
|
||||||
|
set -l t5_start (date +%s)
|
||||||
pushd $gp >/dev/null
|
pushd $gp >/dev/null
|
||||||
agents-vault --silent
|
agents-vault --silent
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
set -l t5_elapsed (math (date +%s) - $t5_start)
|
||||||
|
|
||||||
check "agy knowledge copied" learned (cat $vroot5/agent-vault/global/agy/knowledge/fact.md)
|
check "agy knowledge copied" learned (cat $vroot5/agent-vault/global/agy/knowledge/fact.md)
|
||||||
check "agy settings copied" '{"model":"x"}' (cat $vroot5/agent-vault/global/agy/settings.json)
|
check "agy settings copied" '{"model":"x"}' (cat $vroot5/agent-vault/global/agy/settings.json)
|
||||||
@@ -726,9 +800,27 @@ end
|
|||||||
# Not merely absent from the worktree: absent from the history, which is
|
# Not merely absent from the worktree: absent from the history, which is
|
||||||
# what actually leaves the machine on a push.
|
# what actually leaves the machine on a push.
|
||||||
check "knowledge: no secret reached a commit" false (git -C $vroot5/agent-vault grep -q SECRET-INSIDE-KNOWLEDGE HEAD -- global 2>/dev/null; and echo true; or echo false)
|
check "knowledge: no secret reached a commit" false (git -C $vroot5/agent-vault grep -q SECRET-INSIDE-KNOWLEDGE HEAD -- global 2>/dev/null; and echo true; or echo false)
|
||||||
|
|
||||||
|
# Symlinks: nothing the links name may appear, under any name. The linked
|
||||||
|
# directory must not exist in the vault at all (following it would recreate
|
||||||
|
# its tree wholesale), and the aliased file must not exist either, even
|
||||||
|
# though its own name qualifies -- a dereferencing copy writes a real file
|
||||||
|
# at the link's name and the extension rule waves it through.
|
||||||
|
for escapee in linked linked/leaked.json linked/nested/deep.md alias.md root cyc/loop
|
||||||
|
check "knowledge: symlinked $escapee stayed out" false (test -e $vroot5/agent-vault/global/agy/knowledge/$escapee; and echo true; or echo false)
|
||||||
|
end
|
||||||
|
check "knowledge: nothing outside the store reached a commit" false (git -C $vroot5/agent-vault grep -q SECRET-OUTSIDE-KNOWLEDGE HEAD 2>/dev/null; and echo true; or echo false)
|
||||||
|
# The clock, not the contents: a walk that descends a link to / does not
|
||||||
|
# finish, and this is the every-launch path. Generous enough that a loaded
|
||||||
|
# machine cannot fail it by being slow.
|
||||||
|
check "knowledge: a link to / does not stall the launch path" true (test $t5_elapsed -lt 20; and echo true; or echo false)
|
||||||
# A torn database with its completing write-ahead log deliberately excluded
|
# A torn database with its completing write-ahead log deliberately excluded
|
||||||
# is worse than no database at all, so the scaffold ignores all three.
|
# is worse than no database at all, so the scaffold ignores all three.
|
||||||
check "scaffolded .gitignore excludes *.db" true (grep -qxF '*.db' $vroot5/agent-vault/.gitignore; and echo true; or echo false)
|
check "scaffolded .gitignore excludes *.db" true (grep -qxF '*.db' $vroot5/agent-vault/.gitignore; and echo true; or echo false)
|
||||||
|
# Both stash fallbacks, or a crash mid-rename leaves a shadow copy of an
|
||||||
|
# entry sitting at the vault root for the next `git add -A` to commit.
|
||||||
|
check "scaffolded .gitignore excludes /.adopt-stash" true (grep -qxF '/.adopt-stash' $vroot5/agent-vault/.gitignore; and echo true; or echo false)
|
||||||
|
check "scaffolded .gitignore excludes /.migrate-stash" true (grep -qxF '/.migrate-stash' $vroot5/agent-vault/.gitignore; and echo true; or echo false)
|
||||||
|
|
||||||
check "global claude memory in the vault" global-memory (cat $vroot5/agent-vault/global/claude/memory/g.md)
|
check "global claude memory in the vault" global-memory (cat $vroot5/agent-vault/global/claude/memory/g.md)
|
||||||
check "global claude memory is now a link" true (test -L $chome5/memory; and echo true; or echo false)
|
check "global claude memory is now a link" true (test -L $chome5/memory; and echo true; or echo false)
|
||||||
@@ -1369,6 +1461,33 @@ popd >/dev/null
|
|||||||
set -e __fish_agent_vault_autopush
|
set -e __fish_agent_vault_autopush
|
||||||
check "failing autopush returns non-zero" 1 "$fp2rc"
|
check "failing autopush returns non-zero" 1 "$fp2rc"
|
||||||
|
|
||||||
|
# Autopush runs synchronously in front of every agent launch, so it must be
|
||||||
|
# bounded. Nothing in git bounds it: there is no HTTP connect timeout in
|
||||||
|
# its configuration, http.lowSpeedLimit/http.lowSpeedTime only start
|
||||||
|
# counting once bytes move, and GIT_TERMINAL_PROMPT/GIT_ASKPASS close the
|
||||||
|
# credential prompt rather than the socket. Unwrapped, this remote took
|
||||||
|
# 135s to give up. 192.0.2.1 is TEST-NET-1: reserved, unrouted, and
|
||||||
|
# therefore a blackhole rather than a fast refusal. A network that does
|
||||||
|
# refuse it quickly makes this pass without proving much, which is the
|
||||||
|
# right way round for a test that must never fail spuriously.
|
||||||
|
#
|
||||||
|
# This test costs its own bound in wall time. That is the price of
|
||||||
|
# measuring a timeout, and this defect -- a network call on the launch
|
||||||
|
# path -- has now been introduced twice.
|
||||||
|
set -l blackhole https://192.0.2.1/vault.git
|
||||||
|
agents-vault --remote=$blackhole --silent
|
||||||
|
echo pushed-into-the-void >$croot11/$pmang/memory/p6.md
|
||||||
|
set -g __fish_agent_vault_autopush 1
|
||||||
|
set -l bh_start (date +%s)
|
||||||
|
pushd $pp >/dev/null
|
||||||
|
set -l bhrc (agents-vault --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
set -l bh_elapsed (math (date +%s) - $bh_start)
|
||||||
|
set -e __fish_agent_vault_autopush
|
||||||
|
check "autopush to a blackholed remote returns non-zero" 1 "$bhrc"
|
||||||
|
check "autopush to a blackholed remote is bounded" true (test $bh_elapsed -lt 60; and echo true; or echo false)
|
||||||
|
check "a bounded autopush still committed locally" true (git -C $vroot11/agent-vault ls-files --error-unmatch projects/$pslug/claude/memory/p6.md >/dev/null 2>&1; and echo true; or echo false)
|
||||||
|
|
||||||
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
|
||||||
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
|
||||||
|
|||||||
Reference in New Issue
Block a user