Summary
Six test harnesses define a git_init() (or equivalent) that writes a throwaway identity and disables
commit signing into whatever directory it is handed:
git_init() {
local dir="$1"
git -C "$dir" init -q
git -C "$dir" config user.email t@t.test
git -C "$dir" config user.name test
git -C "$dir" config commit.gpgsign false
git -C "$dir" config core.autocrlf false
}
Nothing verifies that $dir is a fresh temporary repository. If the caller's mktemp -d fails, returns
empty, or a cd does not land where expected, git -C "$dir" config writes into the real repository's
.git/config instead.
Files carrying the pattern:
scripts/check-changelog-parity.test.sh
scripts/affected-tests.test.sh
scripts/check-changed-skills.test.sh
scripts/check-docs-only.test.sh
plugins/claude-ops/skills/plugins/scripts/fleet-state.test.sh
plugins/guardrails/hooks/stale-path-verify.test.sh
Observed, not hypothetical
The working clone's .git/config was found carrying exactly that trio:
user.email=t@t.test
user.name=test
commit.gpgsign=false
Local config overrides global, and every worktree of the clone inherits it. Consequences, all observed:
- Commits came out unsigned, while
main carries an active required_signatures ruleset with
bypass_actors: [] — so the branch could not merge.
- Commits were authored as
test <t@t.test>. GitHub maps that address to no account, so even after the
signing was corrected the commit reported verified: false, reason: no_user. A valid signature does
not rescue an unknown author identity — that cost a second amend cycle to discover.
- The guarded merge helper reports only
mergeStateStatus=BLOCKED in this state, without naming
signatures or identity, so the cause is invisible in its output and has to be found by querying commit
verification by hand.
Two independent pull requests were blocked simultaneously by this before the cause was found.
Why it is worth fixing rather than cleaning up after
The failure is silent, it survives in the clone until someone notices, and every commit made from that
clone or any of its worktrees in the meantime is quietly unmergeable. Nothing in the normal workflow
surfaces it: git commit succeeds, the push succeeds, CI goes green, and only the merge gate refuses —
with a message that does not mention identity or signing.
Suggested fix
Guard git_init so it refuses any directory that is not a fresh throwaway:
- fail closed on an empty or unset
$dir;
- refuse when
$dir resolves inside the current repository's working tree, or when
git -C "$dir" rev-parse --show-toplevel resolves to this repository;
- prefer
git -C "$dir" -c user.email=… -c user.name=… … per invocation over persisting config, or use
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM pointed at a scratch file, so nothing is written to any
repository's .git/config at all.
The per-invocation form removes the whole class: a test that never writes config cannot pollute a
repository, however its temp-dir handling fails.
Whichever shape is chosen, apply it to all six call sites — this is a shared idiom, and fixing one leaves
the others live.
Not established
Which specific invocation polluted this clone, and when. The config was found already present and has been
removed; no reproduction of the failing temp-dir path was attempted, so the mechanism above is derived
from the code rather than observed in the act. The pattern and the resulting config match exactly, but
that is correlation, and the guard is worth adding regardless of which caller did it.
Summary
Six test harnesses define a
git_init()(or equivalent) that writes a throwaway identity and disablescommit signing into whatever directory it is handed:
Nothing verifies that
$diris a fresh temporary repository. If the caller'smktemp -dfails, returnsempty, or a
cddoes not land where expected,git -C "$dir" configwrites into the real repository's.git/configinstead.Files carrying the pattern:
scripts/check-changelog-parity.test.shscripts/affected-tests.test.shscripts/check-changed-skills.test.shscripts/check-docs-only.test.shplugins/claude-ops/skills/plugins/scripts/fleet-state.test.shplugins/guardrails/hooks/stale-path-verify.test.shObserved, not hypothetical
The working clone's
.git/configwas found carrying exactly that trio:Local config overrides global, and every worktree of the clone inherits it. Consequences, all observed:
maincarries an activerequired_signaturesruleset withbypass_actors: []— so the branch could not merge.test <t@t.test>. GitHub maps that address to no account, so even after thesigning was corrected the commit reported
verified: false, reason: no_user. A valid signature doesnot rescue an unknown author identity — that cost a second amend cycle to discover.
mergeStateStatus=BLOCKEDin this state, without namingsignatures or identity, so the cause is invisible in its output and has to be found by querying commit
verification by hand.
Two independent pull requests were blocked simultaneously by this before the cause was found.
Why it is worth fixing rather than cleaning up after
The failure is silent, it survives in the clone until someone notices, and every commit made from that
clone or any of its worktrees in the meantime is quietly unmergeable. Nothing in the normal workflow
surfaces it:
git commitsucceeds, the push succeeds, CI goes green, and only the merge gate refuses —with a message that does not mention identity or signing.
Suggested fix
Guard
git_initso it refuses any directory that is not a fresh throwaway:$dir;$dirresolves inside the current repository's working tree, or whengit -C "$dir" rev-parse --show-toplevelresolves to this repository;git -C "$dir" -c user.email=… -c user.name=… …per invocation over persisting config, or useGIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEMpointed at a scratch file, so nothing is written to anyrepository's
.git/configat all.The per-invocation form removes the whole class: a test that never writes config cannot pollute a
repository, however its temp-dir handling fails.
Whichever shape is chosen, apply it to all six call sites — this is a shared idiom, and fixing one leaves
the others live.
Not established
Which specific invocation polluted this clone, and when. The config was found already present and has been
removed; no reproduction of the failing temp-dir path was attempted, so the mechanism above is derived
from the code rather than observed in the act. The pattern and the resulting config match exactly, but
that is correlation, and the guard is worth adding regardless of which caller did it.