From e7683d1379e73604cd7b5cf6adcbaa21817aeac5 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Wed, 30 Sep 2026 17:25:28 -0400 Subject: [PATCH] fix(agents-repo-sync): refuse a .git that is not a valid gitdir A half-removed AGENTS/.git passed the test -d guard, so git walked up and committed (and bundled) the enclosing repository. Require rev-parse --git-dir to report .git; agents-cleanup uses the same test. --- functions/_agents_repo_sync.fish | 14 ++++++++++---- functions/agents-cleanup.fish | 4 +++- tests/test-agents-cleanup.fish | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/functions/_agents_repo_sync.fish b/functions/_agents_repo_sync.fish index eb2772b..5832ef4 100644 --- a/functions/_agents_repo_sync.fish +++ b/functions/_agents_repo_sync.fish @@ -6,7 +6,9 @@ # # DESCRIPTION # Stages everything in and commits it with . Shared by -# agents-init and agents-vault. +# agents-init, agents-vault and agents-cleanup. must be the root of +# its own repository: a .git directory that is not a valid gitdir is +# refused, because git would otherwise commit the enclosing repository. # # It never touches the network, and that is the point rather than an # omission. Both callers run on every agent launch, synchronously, ahead @@ -36,7 +38,8 @@ # # EXIT STATUS # 0 Committed, or nothing needed committing -# 1 is not a git repository, arguments were missing, or the commit +# 1 is not the root of its own git repository (a broken .git +# directory inside another repository counts as not), arguments were missing, or the commit # itself failed (e.g. a pre-commit/commit-msg hook rejected it) # 2 A rebase is in progress; nothing committed, nothing touched # @@ -48,9 +51,12 @@ # _agents_repo_sync /path/to/AGENTS "chore: sync AGENTS repository" function _agents_repo_sync --argument-names dir msg test -n "$dir" -a -n "$msg"; or return 1 - test -d "$dir/.git"; or return 1 + # Not `test -d "$dir/.git"`: a half-deleted .git directory passes that, and + # git then walks up, finds the enclosing repository and commits *that*. + # A valid repository rooted at reports its git dir as plain ".git". + test "$(git -C "$dir" rev-parse --git-dir 2>/dev/null)" = .git; or return 1 - # The guard above proved .git is a directory, so these are the same two + # The guard above proved has its own .git, so these are the same two # paths `agents-vault --status` reports an unresolved rebase from. if test -d "$dir/.git/rebase-merge"; or test -d "$dir/.git/rebase-apply" echo "_agents_repo_sync: unresolved rebase in $dir; nothing committed" >&2 diff --git a/functions/agents-cleanup.fish b/functions/agents-cleanup.fish index 6e4d905..c4feacc 100644 --- a/functions/agents-cleanup.fish +++ b/functions/agents-cleanup.fish @@ -136,7 +136,9 @@ function agents-cleanup --description 'undo agents-init: restore real files, arc # Resolved, because link targets are compared after realpath, which # also resolves any symlinked parent of $root. set agents_dir (realpath -- "$agents_dir") - test -d "$agents_dir/.git"; and set has_repo 1 + # A .git that is not a valid gitdir (half-removed) is "not a repository"; + # git would otherwise resolve to the enclosing project. + test "$(git -C "$agents_dir" rev-parse --git-dir 2>/dev/null)" = .git; and set has_repo 1 end if test $has_repo -eq 1 diff --git a/tests/test-agents-cleanup.fish b/tests/test-agents-cleanup.fish index a1d4606..7e72ab2 100644 --- a/tests/test-agents-cleanup.fish +++ b/tests/test-agents-cleanup.fish @@ -379,5 +379,19 @@ end check "directive-only: deleted" false (test -e $v1/AGENTS.md -o -L $v1/AGENTS.md; and echo true; or echo false) check "no directive: content identical" "$v2before" (string collect <$v2/AGENTS.md) +section "agents-cleanup: invalid AGENTS/.git never commits the outer repo" +fresh_state +set -l i1 (scaffolded_repo) +echo secret >$i1/untracked-secret.txt +rm -rf $i1/AGENTS/.git/HEAD $i1/AGENTS/.git/objects +set -l i1before (git -C $i1 rev-list --all | count) +pushd $i1 >/dev/null +set -l i1rc (agents-cleanup --silent 2>/dev/null; echo $status) +popd >/dev/null +check "invalid .git: exits 0" 0 "$i1rc" +check "invalid .git: outer repo untouched" "$i1before" (git -C $i1 rev-list --all | count) +check "invalid .git: no bundle" 0 (count $XDG_STATE_HOME/agents-cleanup/*.bundle 2>/dev/null) +check "invalid .git: AGENTS/ removed" false (test -e $i1/AGENTS; and echo true; or echo false) + cleanup report