What
gh-write-guard.py's rule 6 escape hatch is evaluated before the target path is resolved, so it grants writes to every primary checkout the session touches rather than to the one the grant was intended for.
# host-setup/agent-safety/claude/gh-write-guard.py:1046-1050environ=environifenvironisnotNoneelseos.environgrant_value=environ.get("GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT", "").strip().lower()
ifgrant_valuenotin_FALSY_ENV_VALUES:
return"allow", ""The for ... in _all_git_invocations(cmd) loop that resolves resolved, repo_git_dir, and the identity/file dimensions begins at line 1054, after that early return. Nothing about the target reaches the grant decision. The self-test states the same behavior plainly at line 3329: "the escape hatch allows even a denied shape when granted".
Why This Matters
repo-worktree documents setting the grant for the standalone-clone fallback, because that fallback is structurally a primary checkout to the hook's own primary-vs-worktree test and would otherwise be denied the commits it exists to make. That is a legitimate need.
But the grant that unblocks the fallback clone simultaneously unblocks the maintainer's base checkout for the rest of that session. A later git add, git commit, git reset --hard, or branch switch aimed at the base clone is allowed with no further prompt, which is precisely the failure rule 6 exists to prevent, and precisely the incident (#1073) that motivated the whole rule.
The hazard is not theoretical for an agent session: the base clone is usually the working directory the session was launched in, so an inherited-cwd mutation is the easy mistake to make, and the grant removes the one mechanical thing that would have caught it.
The Prose Is Also Stronger Than the Code
GOVERNANCE.md "Repository Boundaries and Write Safety" and .agents/skills/repo-worktree/SKILL.md both describe the grant as scoped to the fallback clone. Neither says it is session-wide and path-blind, which is what it actually is. A reader following either document would not expect the base checkout to be unlocked as a side effect.
Suggested Direction
Not a proposed diff, since the design is the hub's call. Three shapes that would close it, roughly in order of how well they match what the docs already promise:
- Path-scope the grant. Read it as a path (or a colon-separated list) rather than a boolean, and allow only when the resolved target is at or under one of those paths. Both the identity and file dimensions the rule already computes would need to match, so a
--git-dir/--work-tree split cannot straddle the boundary. - Require a separate session for the standalone clone, and say so in the two documents, leaving the boolean grant as-is but never used in a session that also touches a base clone. Cheapest, but relies entirely on discipline, which is what the hook exists to replace.
- Keep the boolean but re-deny the base clone specifically, i.e. grant everything except a checkout that has a linked worktree registered, since a repository with worktrees is exactly the case where another task may be live.
Option 1 is the one that makes the existing prose true as written.
How This Surfaced
Raised by CodeRabbit against a downstream re-vendor of the repo-worktree skill and the GOVERNANCE.md section in ptr727/PhotoCleaner#99, and verified against this repository at mainf3b4cc9 (tag 2.0.526) by reading the hook rather than taking the finding text. Declined downstream and routed here, since neither the hook nor either document is fixable in a carrier: ptr727/PhotoCleaner#99 (comment)
Not This Issue
#1129 covers hub-local references inside the same verbatim-carried files, which is a text defect rather than a behavioral one.#1128 is the analogous "the code is weaker than the sentence describing it" gap in merge-bot-task.yml's handling of the no-auto-merge- marker.
What
gh-write-guard.py's rule 6 escape hatch is evaluated before the target path is resolved, so it grants writes to every primary checkout the session touches rather than to the one the grant was intended for.The
for ... in _all_git_invocations(cmd)loop that resolvesresolved,repo_git_dir, and the identity/file dimensions begins at line 1054, after that early return. Nothing about the target reaches the grant decision. The self-test states the same behavior plainly at line 3329: "the escape hatch allows even a denied shape when granted".Why This Matters
repo-worktreedocuments setting the grant for the standalone-clone fallback, because that fallback is structurally a primary checkout to the hook's own primary-vs-worktree test and would otherwise be denied the commits it exists to make. That is a legitimate need.But the grant that unblocks the fallback clone simultaneously unblocks the maintainer's base checkout for the rest of that session. A later
git add,git commit,git reset --hard, or branch switch aimed at the base clone is allowed with no further prompt, which is precisely the failure rule 6 exists to prevent, and precisely the incident (#1073) that motivated the whole rule.The hazard is not theoretical for an agent session: the base clone is usually the working directory the session was launched in, so an inherited-cwd mutation is the easy mistake to make, and the grant removes the one mechanical thing that would have caught it.
The Prose Is Also Stronger Than the Code
GOVERNANCE.md"Repository Boundaries and Write Safety" and.agents/skills/repo-worktree/SKILL.mdboth describe the grant as scoped to the fallback clone. Neither says it is session-wide and path-blind, which is what it actually is. A reader following either document would not expect the base checkout to be unlocked as a side effect.Suggested Direction
Not a proposed diff, since the design is the hub's call. Three shapes that would close it, roughly in order of how well they match what the docs already promise:
--git-dir/--work-treesplit cannot straddle the boundary.Option 1 is the one that makes the existing prose true as written.
How This Surfaced
Raised by CodeRabbit against a downstream re-vendor of the
repo-worktreeskill and theGOVERNANCE.mdsection in ptr727/PhotoCleaner#99, and verified against this repository atmainf3b4cc9(tag2.0.526) by reading the hook rather than taking the finding text. Declined downstream and routed here, since neither the hook nor either document is fixable in a carrier: ptr727/PhotoCleaner#99 (comment)Not This Issue
#1129covers hub-local references inside the same verbatim-carried files, which is a text defect rather than a behavioral one.#1128is the analogous "the code is weaker than the sentence describing it" gap inmerge-bot-task.yml's handling of theno-auto-merge-marker.