fix(hooks): guard-git.sh branch validation uses the real push target, not the hook's own cwd (#2386) - #2527
Merged
Merged
Conversation
… not the hook's own cwd (#2386) Branch validation fell back to the hook process's own ambient cwd whenever it couldn't parse a -C <dir>/cd <dir> && prefix out of the command text -- exactly what happens for a bare `git push` relying on the Bash tool's persistent cwd from an earlier, separate tool call. For a subagent pushing to a different repository, this validated an unrelated repo's branch and denied a perfectly valid push. guard-git.sh now reads the hook's own top-level `cwd` field (the Bash tool's actual cwd for this call, reported on every PreToolUse payload) as the fallback instead of the hook's ambient cwd, and declines to validate at all when even that isn't available -- a false deny on valid work is worse than a missed check here. The deny message now also names the resolved working directory, and the error text's pattern list now includes dependabot/, matching what the regex already permitted. docs check acknowledged: internal hook bugfix, no README/CLAUDE.md/ ROADMAP surface area changed. Impact: 1 functions changed, 1 affected
Contributor
Greptile SummaryThe PR corrects branch validation for bare push and PR commands by resolving the repository from the PreToolUse payload’s cwd rather than the hook process’s ambient directory.
Confidence Score: 5/5The PR appears safe to merge with no actionable changed-code defects identified. The updated fallback resolves bare commands against the payload cwd, preserves explicit target precedence, avoids consulting an unrelated ambient repository, and is covered by focused regression tests. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
Command["Bash PreToolUse command"] --> Explicit{"Explicit git -C or cd target?"}
Explicit -->|Yes| Target["Use explicit target"]
Explicit -->|No| Payload{"Usable payload cwd?"}
Payload -->|Yes| Target
Payload -->|No| Allow["Decline validation"]
Target --> Branch["Resolve repository HEAD branch"]
Branch --> Pattern{"Allowed branch pattern?"}
Pattern -->|Yes| Allow
Pattern -->|No| Deny["Deny with branch and resolved directory"]
Reviews (1): Last reviewed commit: "fix(hooks): guard-git.sh branch validati..." | Re-trigger Greptile |
Contributor
Codegraph Impact Analysis1 functions changed → 1 callers affected across 1 files
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
guard-git.sh's branch-name validator fell back to the hook process's own ambient cwd whenever it couldn't parse a working directory out of the command string (git -C <dir>orcd <dir> &&). A baregit pushthat relies on the Bash tool's persistent cwd from an earlier, separate tool call matches neither shape, sowork_dirwas empty and the code substituted the hook's own cwd — the session project root, not the repo actually being pushed to. For a subagent pushing to a different repository, this validated an unrelated repo's branch and denied a perfectly valid push, showing the operator a branch name that appeared nowhere in the command they ran.Root cause & fix
Every PreToolUse hook payload carries a top-level
cwdfield — the Bash tool's actual working directory for that specific call — completely independent of whatevercd/-Ctext happens to appear in the command string.guard-git.shwas never reading it.guard-git.shnow extracts this field asHOOK_CWDand uses it asvalidate_branch_name's fallback (after the existing-C/cddetection, which still takes precedence when present).HOOK_CWDisn't usable (missing or not a directory), the hook now declines to validate (allows) rather than guessing against its own ambient cwd — a false deny on valid work is worse than a missed check here, per the issue's own reasoning.dependabot/, matching what the regex already permitted (a display-only inconsistency the issue flagged)..claude/hooks/guard-git.shanddocs/examples/claude-code-hooks/guard-git.shbyte-identical, as enforced by the existingtests/unit/hook-guard-git-clean.test.tssync check.Verification
bash -n/shellcheck: cleantests/unit/hook-guard-git-branch-validation.test.ts(5 tests) reproduces the issue's exact scenario — two real temp git repos, one on an invalid-pattern branch (simulating the orchestrator's own worktree branch) and one on a valid branch (the subagent's actual target), with the hook's payloadcwdand its actual process cwd set independently to prove the fix reads the right one. Verified these tests fail against the pre-fix hook (4/5 failed) before confirming they pass against the fix (5/5), and re-ran with the fix restored (5/5 pass) — not tautological.npx vitest run tests/unit/hook-guard-git-branch-validation.test.ts tests/unit/hook-guard-git-clean.test.ts: 47/47 pass (including the byte-identical docs-sync check)npm run lint: passAlso filed
detect_work_dirhelper and has a milder version of the same gap (silently skips rather than falsely denies); split out as a separate, distinct fix rather than bundled hereCloses #2386