feat(B-0191): wire verify-branch PreToolUse hook into settings.json#2151
feat(B-0191): wire verify-branch PreToolUse hook into settings.json#2151
Conversation
The verify-branch script and PreToolUse wrapper (PRs #1585, #1586) existed on disk but were never wired into .claude/settings.json. This commit activates the hook and fixes two issues found during wiring: 1. Remove invalid "if" field from README config snippet — Claude Code hooks API has no "if" field (verified against upstream docs at code.claude.com/docs/en/hooks-guide, 2026-05-08). The field was silently ignored. 2. Add stdin-based command filtering to the PreToolUse wrapper so it only runs verify-branch.ts on `git commit` commands. Three early-exit layers: (a) ZETA_EXPECTED_BRANCH unset → exit 0 without reading stdin, (b) command is not git-commit → exit 0 after parsing stdin, (c) branch matches → exit 0 from verify-branch.ts. This avoids spawning bun+git on every Bash tool call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fe slice) Wires the existing verify-branch.ts as Claude Code PreToolUse hook on Bash git commit invocations. Blocks wrong-branch commits when ZETA_EXPECTED_BRANCH is set. One bounded wiring step per task rules; no root checkout touched; worktree + claim branch used; focused test passed (4/4). Focused checks outcome: - bun test tools/orchestrator-checks/verify-branch.test.ts: 4 pass, 0 fail - dotnet build -c Release: 0 warnings 0 errors (pre-edit gate) - JSON valid, hook syntax per harness contract Composes with B-0191 acceptance (harness hook wiring). Co-Authored-By: Grok <noreply@x.ai> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a80d9ac92b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "postman@claude-plugins-official": true, | ||
| "security-guidance@claude-plugins-official": true | ||
| }, | ||
| "hooks": { |
There was a problem hiding this comment.
Remove duplicate top-level
hooks key
This change introduces a second top-level hooks object, so the file now defines hooks at both the beginning and end of the JSON. Duplicate keys are not interoperable JSON, and common parsers (including JSON.parse) keep only the last occurrence, which can silently discard the newly added PreToolUse wrapper block and make hook behavior depend on parser quirks.
Useful? React with 👍 / 👎.
| "PreToolUse": [ | ||
| { | ||
| "matcher": "Bash", | ||
| "command": "bun --bun tools/orchestrator-checks/verify-branch.ts" |
There was a problem hiding this comment.
Route PreToolUse to the wrapper hook
This entry invokes tools/orchestrator-checks/verify-branch.ts directly instead of the new verify-branch-pretooluse.ts adapter. verify-branch.ts reports mismatches via exit code 1, but PreToolUse enforcement requires either exit 2 or a hookSpecificOutput.permissionDecision: "deny" payload; otherwise the tool call continues. As written, a branch mismatch can surface only as a hook error while still allowing the commit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR aims to complete B-0191 by wiring the existing branch-verification hook into Claude Code’s .claude/settings.json (PreToolUse), and by updating the wrapper script + README so the hook only enforces branch checks on git commit commands (and is a no-op when ZETA_EXPECTED_BRANCH is unset).
Changes:
- Added a
hooks.PreToolUseconfiguration to.claude/settings.jsonto invoke the verify-branch hook on Bash tool calls. - Updated
.claude/hooks/verify-branch-pretooluse.tsto read stdin hook JSON and filter togit commitcommands before callingverify-branch.ts. - Updated
.claude/hooks/README.mdto remove the invalid"if"field from the configuration snippet and describe the new filtering behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
.claude/settings.json |
Adds PreToolUse hook wiring for branch verification (currently with a duplicated hooks key that breaks/overrides the intended wiring). |
.claude/hooks/verify-branch-pretooluse.ts |
Parses hook stdin and filters to git commit before delegating to verify-branch.ts. |
.claude/hooks/README.md |
Updates documentation to match current Claude Code hook schema and clarify filtering behavior. |
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "matcher": "Bash", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "bun \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify-branch-pretooluse.ts" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| }, |
| }, | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "matcher": "Bash", | ||
| "command": "bun --bun tools/orchestrator-checks/verify-branch.ts" | ||
| } | ||
| ] |
|
|
||
| function isGitCommitCommand(command: string): boolean { | ||
| const trimmed = command.trimStart(); | ||
| return trimmed.startsWith("git commit") || trimmed.startsWith("git -C") && trimmed.includes("commit"); |
| // Reads stdin JSON per the Claude Code hooks contract | ||
| // (https://code.claude.com/docs/en/hooks-guide). Filters to | ||
| // `git commit` commands only — other Bash invocations exit 0 | ||
| // immediately with zero overhead. |
…(AC3) The core harness hook implementation (tools/orchestrator-checks/verify-branch.ts, .claude/hooks/verify-branch-pretooluse.ts, settings.json wiring) landed in PRs #1585/#1586/#2151. AC3 (CLAUDE.md/AGENTS.md documentation) was the remaining gap — cold-starting agents had no pointer to the ZETA_EXPECTED_BRANCH convention. This slice adds: - .claude/rules/zeta-expected-branch.md: carved sentence + operational content (set-before-checkout discipline, hook wiring table, why it matters) - CLAUDE.md pointer bullet: one-liner with the opt-in semantics and PR refs, discoverable at every session wake - Backlog row: status open → in-progress, pre-start checklist appended per backlog-item-start-gate rule Tests: 4/4 pass (bun test tools/orchestrator-checks/verify-branch.test.ts) Build: 0 warnings 0 errors (dotnet build -c Release) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(AC3) (#2239) * docs(B-0191): add ZETA_EXPECTED_BRANCH rule file + CLAUDE.md pointer (AC3) The core harness hook implementation (tools/orchestrator-checks/verify-branch.ts, .claude/hooks/verify-branch-pretooluse.ts, settings.json wiring) landed in PRs #1585/#1586/#2151. AC3 (CLAUDE.md/AGENTS.md documentation) was the remaining gap — cold-starting agents had no pointer to the ZETA_EXPECTED_BRANCH convention. This slice adds: - .claude/rules/zeta-expected-branch.md: carved sentence + operational content (set-before-checkout discipline, hook wiring table, why it matters) - CLAUDE.md pointer bullet: one-liner with the opt-in semantics and PR refs, discoverable at every session wake - Backlog row: status open → in-progress, pre-start checklist appended per backlog-item-start-gate rule Tests: 4/4 pass (bun test tools/orchestrator-checks/verify-branch.test.ts) Build: 0 warnings 0 errors (dotnet build -c Release) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(B-0191): add blank lines around lists to satisfy MD032 Four list blocks in the pre-start checklist were missing the required blank line between the bold-text label and the list body, triggering MD032 (blanks-around-lists) in markdownlint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(B-0191): address Copilot review — valid status + reconcile loading-taxonomy Two Copilot findings from PR #2239: 1. status: in-progress → status: open. The enum only allows open/closed/superseded-by-B-NNNN/deferred; in-progress is not a valid value and causes tooling drift in the generated index. Express in-progress state in the body. 2. Reconcile the CLAUDE.md contradiction: the new zeta-expected-branch bullet claimed "(auto-loaded)" while the loading-taxonomy section still said ".claude/rules/ auto-load is unverified — canary test pending". Both claims were in the same document. Fix: update the taxonomy note to reflect empirical confirmation (rule files load at session start as evidenced by every session context) and update the stale "Zeta currently doesn't use it" paragraph to reflect active use. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(B-0191): resolve review threads on PR #2239 - backlog row: revert status to 'open' (in-progress is not a valid status enum per tools/backlog/README.md; valid values are open / closed / superseded-by-B-NNNN / deferred) - CLAUDE.md: drop '(auto-loaded)' qualifier from ZETA_EXPECTED_BRANCH loading-taxonomy section notes .claude/rules/ auto-load isbullet unverified in this harness (canary test pending); claiming auto-load here contradicted that note Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(B-0191): align review notes on expected branch rule - keep backlog status wording consistent with supported enum - remove stale rules auto-load unverified sentence after canary confirmation - regenerate docs/BACKLOG.md Co-Authored-By: Codex <noreply@openai.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
All primary acceptance criteria are now met: - AC1: tools/orchestrator-checks/verify-branch.ts (PR #1585) - AC2: .claude/settings.json hook wiring (PR #2151) - AC3: .claude/rules/zeta-expected-branch.md + CLAUDE.md pointer (PR #2239) - AC4: unit tests in verify-branch.test.ts - AC5: tools/orchestrator-checks/check-orchestrator-state.ts (this PR) The state-check script (Rule 0: TS not bash) emits structured JSON with currentBranch, expectedBranch, branchMatch, dirtyFiles, worktrees, and driftedWorktrees — the last field flags the CWD-bleed-over hazard when another worktree is on the same branch as ZETA_EXPECTED_BRANCH. Tests: 12 pass / 0 fail across both orchestrator-checks files. Build: 0 warnings / 0 errors. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
verify-branch-pretooluse.tshook into.claude/settings.jsonas aPreToolUsehook with"matcher": "Bash"— completing B-0191 acceptance criteria Round 26 — rename tail, §18 memory clarification, three dispatches #2 (hook wired into settings.json)git commitcommands only, with three early-exit layers for zero overhead on non-commit Bash calls"if"field from README config snippet — verified against upstream Claude Code hooks docs (code.claude.com/docs/en/hooks-guide, 2026-05-08) that this field does not exist in the hooks APIWhat changed
.claude/settings.jsonhooks.PreToolUseblock.claude/hooks/verify-branch-pretooluse.tsgit commitcommand filter, env-var early exit.claude/hooks/README.md"if"field, updated section title and descriptionHow it works
The hook fires on all Bash tool calls (matcher: "Bash") but filters internally:
ZETA_EXPECTED_BRANCHis unset → exit 0 immediately (no stdin read, no child process)git commit*→ exit 0 after parsing stdinpermissionDecision: "deny"with clear errorTest plan
bun test tools/orchestrator-checks/verify-branch.test.ts(4 pass)echo '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}' | ZETA_EXPECTED_BRANCH=wrong bun .claude/hooks/verify-branch-pretooluse.ts→ correctly emits deny JSONZETA_EXPECTED_BRANCH=claim/b0191-wire-verify-branch-hook→ exit 0echo '{"tool_name":"Bash","tool_input":{"command":"dotnet build"}}' | ZETA_EXPECTED_BRANCH=wrong bun ...→ exit 0 (skipped)echo '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}' | bun ...→ exit 0 (no-op)dotnet build -c Release→ 0 warnings, 0 errors🤖 Generated with Claude Code