Skip to content

fix(hooks): guard-git.sh branch validation uses the real push target, not the hook's own cwd (#2386) - #2527

Merged
carlos-alm merged 1 commit into
mainfrom
fix/issue-2386
Aug 15, 2026
Merged

fix(hooks): guard-git.sh branch validation uses the real push target, not the hook's own cwd (#2386)#2527
carlos-alm merged 1 commit into
mainfrom
fix/issue-2386

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

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> or cd <dir> &&). A bare git push that relies on the Bash tool's persistent cwd from an earlier, separate tool call matches neither shape, so work_dir was 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 cwd field — the Bash tool's actual working directory for that specific call — completely independent of whatever cd/-C text happens to appear in the command string. guard-git.sh was never reading it.

  • guard-git.sh now extracts this field as HOOK_CWD and uses it as validate_branch_name's fallback (after the existing -C/cd detection, which still takes precedence when present).
  • When even HOOK_CWD isn'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.
  • The deny message now names the resolved working directory alongside the branch, so a wrong resolution is obvious immediately.
  • The error text's pattern list now includes dependabot/, matching what the regex already permitted (a display-only inconsistency the issue flagged).
  • Kept .claude/hooks/guard-git.sh and docs/examples/claude-code-hooks/guard-git.sh byte-identical, as enforced by the existing tests/unit/hook-guard-git-clean.test.ts sync check.

Verification

  • bash -n / shellcheck: clean
  • New test file tests/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 payload cwd and 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: pass

Also filed

Closes #2386

… 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
@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The 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.

  • Adds payload cwd extraction and uses it as the branch-resolution fallback.
  • Declines validation when no repository can be resolved and improves denial diagnostics.
  • Keeps the live and documented hooks synchronized and adds focused regression coverage.

Confidence Score: 5/5

The 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

Filename Overview
.claude/hooks/guard-git.sh Reads the command’s payload cwd, uses it for branch resolution when no explicit target exists, and fails open when resolution is impossible.
docs/examples/claude-code-hooks/guard-git.sh Mirrors the live hook changes to preserve the documented-hook synchronization contract.
tests/unit/hook-guard-git-branch-validation.test.ts Adds regression tests separating the hook process cwd from the command payload cwd and covering precedence, denial diagnostics, and unresolved targets.

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"]
Loading

Reviews (1): Last reviewed commit: "fix(hooks): guard-git.sh branch validati..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

1 functions changed1 callers affected across 1 files

  • validate_branch_name in docs/examples/claude-code-hooks/guard-git.sh:233 (1 transitive callers)

@carlos-alm
carlos-alm merged commit 81b554d into main Aug 15, 2026
27 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2386 branch August 15, 2026 21:29
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(hooks): guard-git.sh branch validation falls back to the session's branch, denying valid subagent pushes to other repos

1 participant