Repository navigation
Description
Summary
Running the repository's pre-commit suite from Git's pre-commit hook can corrupt the repository that invoked it. Git exports repository-local GIT_* variables to hooks; the Bats suite creates nested fixture repositories with plain git -C <fixture> init / git -C <fixture> commit without clearing those variables. Under the hook, those commands target the parent repository's gitdir instead of the fixture.
This was reproduced twice while resolving the approved #564 / #504 merge.
Observed impact
The global hook (~/.config/git/hooks/pre-commit) invokes the repo's pre-commit suite. During that run:
-
test/bats/116-clone-option.bats::_mk_reporan:git -C "$1" init -q git -C "$1" -c [email protected] -c user.name=t commit -q -m init
-
Because hook-local
GIT_DIR/ related variables were inherited, the test commit landed in the parent quickstart repository, not$STUBDIR/cloneproj. The worktree reflog records the test identity:de6b9b3 ... d28b252 t <[email protected]> ... commit (merge): init -
The shared repository config gained:
core.worktree=/private/tmp/qs564-merge -
A later real kit-fetch path (
kit_translate_fetch) rangit fetch+git checkout FETCH_HEAD; because the parent gitdir/worktree had been captured, it checked out the patterns bundle'sFETCH_HEAD(56d1f5f, patterns release 1.12.0) into the quickstart worktree. The primary quickstart checkout then appeared as mass deletions plus untracked patterns files.
No push occurred. The remote and local worktrees were repaired, but unstaged local edits could have been lost if not independently preserved.
Root cause
The Bats harness isolates HOME, XDG_CONFIG_HOME, state dirs, and backend knobs, but does not clear Git's repository-local environment before creating a different repository:
# test/bats/helper.bash:41-61
acq_setup_stubs() {
...
export HOME="$STUBDIR/home"
export XDG_CONFIG_HOME="$STUBDIR/xdg"
unset GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM
...
}Git's documented pattern before operating on another repository is:
unset $(git rev-parse --local-env-vars)(or an equivalent explicit allowlist/clean environment). git -C changes the working directory but does not override an inherited GIT_DIR.
There is also no fail-closed guard in kit_translate_fetch preventing DESTDIR from resolving to the active repository root before it executes:
cd "$destdir"
rm -rf .git
git init -q
git fetch ...
git checkout -q FETCH_HEADThe test isolation bug is the trigger; the missing destination guard magnifies it into destructive checkout.
Proposed fix
-
In the Bats setup, clear repository-local Git environment before any nested fixture repo is created, while retaining intentionally-set identity variables per test:
while IFS= read -r name; do unset "$name"; done < <(git rev-parse --local-env-vars)
Use a Bash-3.2-compatible form if process substitution is undesirable.
-
Add a regression test that runs
_mk_repowithGIT_DIR/GIT_WORK_TREEpointing at a sentinel parent repo and proves the parent HEAD/config/worktree do not change. -
Defense-in-depth in
kit_translate_fetch: reject emptyDESTDIR,/, and a destination resolving to the current repository root beforerm -rf .git/git init. Test each refusal and verify no Git command runs. -
Consider running repo tests from pre-commit with Git-local env cleared centrally in the hook wrapper as an additional boundary, but the repository tests must remain safe on their own.
Acceptance
- Full pre-commit suite can run from a real Git hook without changing parent
HEAD, branch,core.worktree, remotes, or tracked files - Nested fixture repos still work in direct
batsruns and CI - Regression test proves inherited
GIT_DIRcannot redirect_mk_repo -
kit_translate_fetchrefuses an empty/root/current-repo destination before destructive Git operations - fix(msb): route ssh-agent forward through a managed symlink #564 can be rebased onto
mainand committed with hooks enabled
Related
- fix(msb): route ssh-agent forward through a managed symlink #564 — the commit operation that exposed the defect
- Carry #564's ssh-agent symlink edits through the #504 rebase (1 conflict hunk + 8 test seeds) #567 — measured fix(msb): route ssh-agent forward through a managed symlink #564/docs(adr): add ADR-0035 host-authoritative sandbox configuration #504 integration steps
test/bats/116-clone-option.bats:25-30test/bats/helper.bash:41-61acq.backends/kit-translate.sh:54-127
Activity
wz-gsa commentedon Oct 23, 2026
Follow-up implementation of PR596: bare-repository guard fix and two regression tests are locally complete; red/green confirmed,27focused tests pass and independent review approves. Before commit, git var GIT_AUTHOR_IDENT revealed repository-local user.name=t and user.email=[email protected] overriding the verified global identity. Origin is correct. Time/cause of the shared-config mutation is not established. Stopped before commit; no identity override or hook bypass applied. Human authorization needed to remove the two fixture identity overrides from local config. Merge from current main is staged in isolated worktree, latest fix/tests remain uncommitted.
fix(kits): preserve repositories and recover interrupted fetches