Conversation
…ME (B-0191 #2 + #3) Closes B-0191 acceptance criterion #2 (hook wired -- but opt-in via settings.json edit, not auto-activated) and #3 (README in .claude/hooks/ explaining the wiring). Per Otto-364 search-first authority: WebSearched the canonical Anthropic Claude Code hooks reference at https://code.claude.com/docs/en/hooks before designing the wrapper. Schema: PreToolUse hook returns hookSpecificOutput JSON with permissionDecision: "allow" | "deny" | "ask" | "defer", or exits 0 (allow) / exits 2 (block with stderr as reason). Implementation choice: wrapper translates verify-branch.ts's exit code + stderr to the PreToolUse JSON contract. Default-off behavior preserved -- the script files exist on disk, but no harness behavior changes until .claude/settings.json is explicitly edited to wire the hook. This avoids unilaterally changing commit flow for any contributor running this repo. Wiring is documented in .claude/hooks/README.md with the exact settings.json snippet to add. The "if" clause restricts the hook to "Bash(git commit*)" so other Bash invocations are unaffected. Composes with: - tools/orchestrator-checks/verify-branch.ts (PR #1585) -- the underlying check that this wraps - memory/feedback_orchestrator_pre_commit_verify_branch_rule_aaron_2026_05_04.md (PR #1568) -- the manual discipline being mechanized - memory/feedback_dst_justifies_ts_quality_over_bash_and_harness_hooks_suffice_no_git_hooks_aaron_2026_05_03.md -- the harness-hooks-suffice rule Acceptance criterion #4 (test: deliberate wrong-branch commit blocked) already covered by tools/orchestrator-checks/verify-branch.test.ts (4 tests passing). The wrapper itself is thin and integration-tested when the hook is opted in. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d79ccc51c8
ℹ️ 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".
| // Run verify-branch.ts. We don't need to parse the stdin JSON because | ||
| // verify-branch.ts reads ZETA_EXPECTED_BRANCH from env + queries git | ||
| // directly -- the tool_input.command isn't needed for the check. |
There was a problem hiding this comment.
Parse Bash target repo before running branch check
This wrapper assumes tool_input.command is irrelevant and always runs verify-branch.ts in the hook process context, which means the check can validate the wrong repository when the commit command targets another cwd (for example git -C ../other-repo commit ... or a command that changes directory before committing). In those cases the hook may allow a commit even though the actual commit target branch does not match ZETA_EXPECTED_BRANCH, undermining the branch-safety guarantee this hook is meant to enforce.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a Claude Code PreToolUse wrapper around the existing branch-verification script and documents how contributors can opt into that hook locally. This fits the repo’s harness-integration layer by exposing tools/orchestrator-checks/verify-branch.ts through .claude/hooks/ without changing default behavior.
Changes:
- Adds
.claude/hooks/verify-branch-pretooluse.tsto translateverify-branch.tsexit status/stderr into Claude Code’s PreToolUse response shape. - Adds
.claude/hooks/README.mdwith opt-in.claude/settings.jsonwiring instructions and per-task usage guidance. - Keeps the feature default-off unless contributors explicitly edit
.claude/settings.json.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
.claude/hooks/verify-branch-pretooluse.ts |
New Bun wrapper that invokes the existing branch check and emits Claude hook allow/deny behavior. |
.claude/hooks/README.md |
New documentation describing the wrapper, opt-in settings snippet, and expected workflow. |
| Add this block to the top-level object in `.claude/settings.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "matcher": "Bash", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "if": "Bash(git commit*)", | ||
| "command": "bun \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify-branch-pretooluse.ts" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } |
| # `.claude/hooks/` -- harness pre-tool-use hooks | ||
|
|
||
| Claude Code reads project-level hooks from `.claude/settings.json`. Hook scripts live here. Wiring a hook into the harness requires editing `.claude/settings.json`; the script existing on disk does NOT make it active by itself (opt-in is explicit). | ||
|
|
||
| Canonical Anthropic reference: <https://code.claude.com/docs/en/hooks>. | ||
|
|
||
| ## Available hooks | ||
|
|
||
| ### `verify-branch-pretooluse.ts` | ||
|
|
||
| Wraps `tools/orchestrator-checks/verify-branch.ts` (PR #1585) into the Claude Code PreToolUse JSON contract. Mechanizes the orchestrator branch-verify rule (per B-0191) -- when `ZETA_EXPECTED_BRANCH` is set in the session env and `git branch --show-current` doesn't match, the hook blocks the `git commit` Bash invocation with `permissionDecision: "deny"` and the script's stderr as the reason. | ||
|
|
||
| If `ZETA_EXPECTED_BRANCH` is unset, the hook is a no-op (exits 0, allow). The default-off behavior means wiring this hook does not change any commit flow unless an agent (or maintainer) explicitly sets the env var for a task. | ||
|
|
||
| #### Opt-in configuration |
| Add this block to the top-level object in `.claude/settings.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "matcher": "Bash", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "if": "Bash(git commit*)", | ||
| "command": "bun \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify-branch-pretooluse.ts" |
| function emitDeny(reason: string): never { | ||
| const output: HookOutput = { | ||
| hookSpecificOutput: { | ||
| hookEventName: "PreToolUse", | ||
| permissionDecision: "deny", | ||
| permissionDecisionReason: reason, | ||
| }, | ||
| }; | ||
| console.log(JSON.stringify(output)); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| function main(): number { | ||
| // Run verify-branch.ts. We don't need to parse the stdin JSON because | ||
| // verify-branch.ts reads ZETA_EXPECTED_BRANCH from env + queries git | ||
| // directly -- the tool_input.command isn't needed for the check. | ||
| const projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd(); | ||
| const result = spawnSync( | ||
| "bun", | ||
| [`${projectDir}/tools/orchestrator-checks/verify-branch.ts`], | ||
| { | ||
| encoding: "utf8", | ||
| stdio: ["ignore", "pipe", "pipe"], | ||
| }, | ||
| ); | ||
|
|
||
| if (result.status === 0) { | ||
| // Allowed -- forward any stderr (worktree-warning) and exit 0. | ||
| if (result.stderr) { | ||
| process.stderr.write(result.stderr); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // Blocked -- translate to deny JSON with the script's stderr as reason. | ||
| const reason = (result.stderr || "verify-branch check failed").trim(); | ||
| emitDeny(reason); |
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>
…2151) * feat(B-0191): wire verify-branch PreToolUse hook into settings.json 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> * hook(B-0191): wire verify-branch harness PreToolUse hook (smallest safe 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> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Grok <noreply@x.ai> Co-authored-by: Cursor <cursoragent@cursor.com>
…(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>
Summary
Closes B-0191 acceptance criterion #2 (hook wired) and #3 (README explaining the wiring). Default-off opt-in: the script files exist on disk, but no harness behavior changes until
.claude/settings.jsonis explicitly edited.Per Otto-364 search-first authority
WebSearched the canonical Anthropic Claude Code hooks reference before designing the wrapper. Schema: PreToolUse hook returns
hookSpecificOutputJSON withpermissionDecision: "allow" | "deny" | "ask" | "defer", or exits 0 (allow) / exits 2 (block with stderr as reason).Files
.claude/hooks/verify-branch-pretooluse.ts-- thin wrapper translatingtools/orchestrator-checks/verify-branch.ts(PR feat(orchestrator-checks): verify-branch.ts harness hook (B-0191 implementation) #1585) exit code + stderr to the PreToolUse JSON contract..claude/hooks/README.md-- opt-in instructions including the exact.claude/settings.jsonsnippet, theif: "Bash(git commit*)"restriction, and per-task usage pattern.Why default-off
Adding a PreToolUse hook would fire on every
git commitBash invocation -- even though it's a no-op whenZETA_EXPECTED_BRANCHis unset, the wiring change still affects every contributor / agent on the repo. Per the don't-ask-permission-within-authority-scope rule, I have authority to ship the script; per the falsifiability discipline + responsible-engineering, the wiring stays opt-in via explicit settings.json edit. Aaron (or any future-Otto with a specific need) can wire it with a one-line config change.Composes with
memory/feedback_dst_justifies_ts_quality_over_bash_and_harness_hooks_suffice_no_git_hooks_aaron_2026_05_03.mdB-0191 acceptance criteria status after this PR merges
.claude/hooks/README.md(this PR)🤖 Generated with Claude Code