You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(worktrees): a test's git writes hit the live repo under an ambient GIT_DIR, and every fresh worktree starts with 41 stageable mode-only diffs #15246
Found while fixing #15176, in two forms — one a latent trap, one an actual corruption that happened.
1. Every fresh worktree starts with 41 uncommitted mode-only diffs
The post-checkout hook chmods 40 hook scripts, so a newly created worktree immediately shows 41 files changed 100644 → 100755 with no content difference.
Any git add -A silently commits them. They do not appear in a diff review — a mode-only change shows as a modified file with no visible content — so they ride along in whatever commit is being made and land in an unrelated PR. With 230+ worktrees in play, and agents that stage broadly, this is a steady low-level source of unexplained diff noise.
2. The same ambient-GIT_DIR defect, with a write instead of a read
autobot_shared/paths_test.py:204-212 — its _git helper passed env={**os.environ, ...}, inheriting an ambient GIT_DIR.
Under the pre-push hook — which exports GIT_DIR when the checkout is a worktree, and every checkout here is — that test's git add -A and git commit -m initial executed against the pushing worktree's own git directory, with a pytest temp directory as the work tree. It wrote a bogus commit onto the live branch that dropped 86 tracked files from the index.
Recovered: no data lost (the affected files are gitignored-but-tracked and were untouched on disk), the branch was reset onto the good commit, and the tree verified back at 9637 tracked files with no other worktree or branch affected. The specific instance is fixed in PR #15243 with the same scrubbed_git_env() helper as the rest of #15176.
It is filed anyway, because the class is worse than the instance.#15176 is about guards reading the wrong root and reporting nonsense. This is a test writing to the wrong repository — and it was in the test file of the very module being changed, reachable by nothing more unusual than pushing from a worktree.
Acceptance criteria
A fresh worktree does not start dirty. Either the post-checkout hook stops rewriting modes that are already correct in the index, or the modes are corrected once at source so the hook is a no-op. Establish which before changing anything — a hook that exists to fix a real mode problem should not simply be deleted.
git add -A in a fresh worktree stages nothing unexpected. Demonstrate it.
Sweep for other tests that run git write commands with an inherited environment. One found while changing an unrelated file is not evidence there is one. Prioritise anything running add, commit, reset, checkout or push.
Any such test scrubs the git environment, so a temp-directory repository cannot be confused with the real one.
A test that fails if an inherited-environment git write is reintroduced. The contrast mutation for it: restore the inherited env and show it fails.
The mode noise blocks worktree disposal — sixth independent sighting
This started as cosmetic. It is not.
An agent finishing its work tried to remove its own worktree after verifying the content had landed, and git refused:
fatal: ... contains modified or untracked files, use --force to delete it
The only thing dirtying that tree was the 40 mode-only 100644 => 100755 entries this issue describes. Verified before the decision: 0 added and 0 deleted content lines, 0 untracked files, no non-mode entries in git status. It declined to --force — correctly, since a dirty tree refusing to go is information, and a force would have been indistinguishable from forcing past real uncommitted work.
So every worktree this hook touches becomes undisposable by the normal command, and the only ways past are --force (which defeats the protection for every future case) or -c core.fileMode=false (which one agent already needed to complete a rebase). That compounds directly with the 230+ worktrees on this machine: work finishes, the tree cannot be cleaned up cleanly, and the count grows.
Six independent sightings today
Reported separately by six agents that had no knowledge of each other's runs:
three while working unrelated issues, each noting 40–41 mode-only entries in a fresh worktree;
one that needed -c core.fileMode=false to rebase past them;
one that had to keep them unstaged deliberately to avoid committing them into an unrelated PR;
this one, blocked from disposal.
That spread is the finding. It is not an occasional artefact — it is the default state of every worktree created on this machine, and it interferes with staging, rebasing and cleanup in turn.
What that adds to the criteria
The existing criteria ask that a fresh worktree not start dirty. Add:
git worktree remove succeeds on a finished worktree without --force. That is the operational symptom, and it is the one that grows the worktree count.
Whatever fix lands does not rely on core.fileMode=false as the standing answer. That hides real mode changes as well as spurious ones, and this repository has hook scripts whose executable bit genuinely matters — check_hook_exec_bits.py exists to enforce exactly that.
Another live instance of the same class, found working #15273 in worktree issue-15273-hardcoded-scope.
autobot-infrastructure/shared/scripts/hooks/pre-commit-hardcoded-values_test.py has ~20 tests (_run_hook_with_staged, _git) that each do git init / git add in a fresh tmp_path, with no env scrubbing — subprocess.run(["git", *args], cwd=repo, ...), ambient env inherited. tools/git-hooks/pre-push invokes pytest directly (tools/git-hooks/pre-push:190-195) with no unset GIT_DIR/env -u before it, same shape as the paths_test.py instance already fixed in PR #15243.
Reproduced by git push from a worktree (ambient GIT_DIR pointing at .git/worktrees/<name>): the suite's temp-repo git add calls landed in the real worktree index instead — git status afterward showed ~20 bogus A/AD entries (src/worker.py, deploy.yaml, ssot_config.py, etc., all from the test fixtures) plus two real tracked files (README.md, repo_tests/sdk_request_shared.py) with their index blob overwritten by 1-line test content while the working-tree copy stayed correct (156/461 lines intact — index-only corruption, not a bad commit this time, presumably because no git commit step ran that far before a test failed).
Recovered with git reset (mixed, no path) — restores the index to HEAD without touching the working tree, which was never touched. No data lost.
Not fixed here — out of scope for #15273 and #15246's own AC calls for a full sweep rather than a one-off patch. Filing this as evidence for that sweep: pre-commit-hardcoded-values_test.py's _git/_run_hook_with_staged helpers should use the same scrubbed_git_env() helper #15243 introduced.
Delivered earlier by #15253/#15262 — the post-checkout hook now gates its chmod on a shebang, and 22 stale modes were committed. Established which of the two options applied before changing anything, as this issue required: the hook was fixing a real problem, so the modes were corrected at source rather than the hook deleted
git add -A in a fresh worktree stages nothing unexpected
MET
A fresh git worktree add produces 0 porcelain entries, demonstrated at the time and re-verified during this PR
Sweep for other tests running git writes with an inherited environment
MET
~60 candidate files classified by hand; ~27 had genuine unscrubbed writes, all fixed
Any such test scrubs the git environment
MET
Via the established scrubbed_git_env() from #15243 — no second mechanism invented
A test that fails if an inherited-environment write is reintroduced
MET
tools/lint/check_git_write_env_scrubbed.py, wired at .pre-commit-config.yaml:300, tracked 100755 with a shebang on line 1, reach floor TEST_FILE_FLOOR = 1500 against 2064 tracked test files
The census framing was wrong twice, and both corrections mattered
The original sweep searched only synchronous subprocess.* and missed asyncio.create_subprocess_exec / create_subprocess_shell entirely. Any future sweep for this defect class that greps subprocess.run alone will report a clean tree that is not clean.
The census was also scoped to test files, which is why the worst instance was found only by review. services/task_workspace.py ran git worktree remove --force, git branch -D and git worktree add with no env= at all, and tests/test_task_workspace.py calls those functions in-process, dozens of times, against a tmp_path fixture. In-process is the crux: scrubbing the test's own fixture protects nothing, because the production function builds its own subprocess environment. Under the pre-push hook's ambient GIT_DIR that suite would have registered and removed worktrees and force-deleted branches in the real repository — strictly worse than the incident that prompted this issue, which dropped files from an index and was recoverable. git branch -D destroys commits not merged elsewhere. Now scrubbed at all four sites (task_workspace.py:224,234,359,372).
A re-sweep under the corrected framing — production code reachable in-process from a test that constructs a temporary repository — found zero further sites.
The guard could not run, and said so itself
On first push the new guard was tracked non-executable, so pre-commit could not execute it. verify-precommit-config caught it: "A hook that cannot execute reports nothing about the code, so a green check over it is a false clean bill of health." The guard against tests silently corrupting the repository was itself silently not running, and would have reported clean indefinitely. Fixed with git update-index --chmod=+x — core.fileMode=false is set repo-wide, so a plain chmod never reaches the index and the mismatch is invisible locally.
Deliberately out of scope, tracked
The guard scans test files only. Production callers span MCP tools, Celery workers and CLI scripts, and need per-site caller verification rather than a mechanical env= insertion — exactly what task_workspace.py required. #15287 holds ten further production sites, one of which (skills/promoter.py) runs git add and git commit with neither cwd= nor env=, so a scrub alone would not fix it.
Activity
mrveiss commentedon Aug 28, 2026
The mode noise blocks worktree disposal — sixth independent sighting
This started as cosmetic. It is not.
An agent finishing its work tried to remove its own worktree after verifying the content had landed, and git refused:
The only thing dirtying that tree was the 40 mode-only
100644 => 100755entries this issue describes. Verified before the decision: 0 added and 0 deleted content lines, 0 untracked files, no non-mode entries ingit status. It declined to--force— correctly, since a dirty tree refusing to go is information, and a force would have been indistinguishable from forcing past real uncommitted work.So every worktree this hook touches becomes undisposable by the normal command, and the only ways past are
--force(which defeats the protection for every future case) or-c core.fileMode=false(which one agent already needed to complete a rebase). That compounds directly with the 230+ worktrees on this machine: work finishes, the tree cannot be cleaned up cleanly, and the count grows.Six independent sightings today
Reported separately by six agents that had no knowledge of each other's runs:
-c core.fileMode=falseto rebase past them;That spread is the finding. It is not an occasional artefact — it is the default state of every worktree created on this machine, and it interferes with staging, rebasing and cleanup in turn.
What that adds to the criteria
The existing criteria ask that a fresh worktree not start dirty. Add:
git worktree removesucceeds on a finished worktree without--force. That is the operational symptom, and it is the one that grows the worktree count.core.fileMode=falseas the standing answer. That hides real mode changes as well as spurious ones, and this repository has hook scripts whose executable bit genuinely matters —check_hook_exec_bits.pyexists to enforce exactly that.Refs #15176, PR #15243.
mrveiss commentedon Aug 29, 2026
Another live instance of the same class, found working #15273 in worktree
issue-15273-hardcoded-scope.autobot-infrastructure/shared/scripts/hooks/pre-commit-hardcoded-values_test.pyhas ~20 tests (_run_hook_with_staged,_git) that each dogit init/git addin a freshtmp_path, with no env scrubbing —subprocess.run(["git", *args], cwd=repo, ...), ambient env inherited.tools/git-hooks/pre-pushinvokespytestdirectly (tools/git-hooks/pre-push:190-195) with nounset GIT_DIR/env -ubefore it, same shape as thepaths_test.pyinstance already fixed in PR #15243.Reproduced by
git pushfrom a worktree (ambientGIT_DIRpointing at.git/worktrees/<name>): the suite's temp-repogit addcalls landed in the real worktree index instead —git statusafterward showed ~20 bogusA/ADentries (src/worker.py,deploy.yaml,ssot_config.py, etc., all from the test fixtures) plus two real tracked files (README.md,repo_tests/sdk_request_shared.py) with their index blob overwritten by 1-line test content while the working-tree copy stayed correct (156/461 lines intact — index-only corruption, not a bad commit this time, presumably because nogit commitstep ran that far before a test failed).Recovered with
git reset(mixed, no path) — restores the index toHEADwithout touching the working tree, which was never touched. No data lost.Not fixed here — out of scope for #15273 and #15246's own AC calls for a full sweep rather than a one-off patch. Filing this as evidence for that sweep:
pre-commit-hardcoded-values_test.py's_git/_run_hook_with_stagedhelpers should use the samescrubbed_git_env()helper #15243 introduced.fix(backend): scrub the git environment in task_workspace's worktree/…
fix(guards): mark check_git_write_env_scrubbed.py executable (#15246)
fix(guards): suppress the print/console false regression in the new g…
fix(guards): lower the size-ratchet ceiling for check-pre-commit-hook…
fix(test): scrub the git environment in every test-fixture git write (#…
mrveiss commentedon Aug 29, 2026
Verified in merged
Dev_new_gui— closingPR #15286 merged as
6147fd069.chmodon a shebang, and 22 stale modes were committed. Established which of the two options applied before changing anything, as this issue required: the hook was fixing a real problem, so the modes were corrected at source rather than the hook deletedgit add -Ain a fresh worktree stages nothing unexpectedgit worktree addproduces 0 porcelain entries, demonstrated at the time and re-verified during this PRgitwrites with an inherited environmentscrubbed_git_env()from #15243 — no second mechanism inventedtools/lint/check_git_write_env_scrubbed.py, wired at.pre-commit-config.yaml:300, tracked100755with a shebang on line 1, reach floorTEST_FILE_FLOOR = 1500against 2064 tracked test filesThe census framing was wrong twice, and both corrections mattered
The original sweep searched only synchronous
subprocess.*and missedasyncio.create_subprocess_exec/create_subprocess_shellentirely. Any future sweep for this defect class that grepssubprocess.runalone will report a clean tree that is not clean.The census was also scoped to test files, which is why the worst instance was found only by review.
services/task_workspace.pyrangit worktree remove --force,git branch -Dandgit worktree addwith noenv=at all, andtests/test_task_workspace.pycalls those functions in-process, dozens of times, against atmp_pathfixture. In-process is the crux: scrubbing the test's own fixture protects nothing, because the production function builds its own subprocess environment. Under the pre-push hook's ambientGIT_DIRthat suite would have registered and removed worktrees and force-deleted branches in the real repository — strictly worse than the incident that prompted this issue, which dropped files from an index and was recoverable.git branch -Ddestroys commits not merged elsewhere. Now scrubbed at all four sites (task_workspace.py:224,234,359,372).A re-sweep under the corrected framing — production code reachable in-process from a test that constructs a temporary repository — found zero further sites.
The guard could not run, and said so itself
On first push the new guard was tracked non-executable, so pre-commit could not execute it.
verify-precommit-configcaught it: "A hook that cannot execute reports nothing about the code, so a green check over it is a false clean bill of health." The guard against tests silently corrupting the repository was itself silently not running, and would have reported clean indefinitely. Fixed withgit update-index --chmod=+x—core.fileMode=falseis set repo-wide, so a plainchmodnever reaches the index and the mismatch is invisible locally.Deliberately out of scope, tracked
The guard scans test files only. Production callers span MCP tools, Celery workers and CLI scripts, and need per-site caller verification rather than a mechanical
env=insertion — exactly whattask_workspace.pyrequired. #15287 holds ten further production sites, one of which (skills/promoter.py) runsgit addandgit commitwith neithercwd=norenv=, so a scrub alone would not fix it.test(repo): run the conflict detector instead of restating it (#17297)
fix(repo): arm the conflict-marker gate and untrack the file it missed (
fix(lint): the toplevel gate never saw the git hooks, and one carried…
fix(hooks): keep the installed pre-push in step with its tracked copy (…