Skip to content

feat(claude-hooks): verify-branch-pretooluse.ts wrapper + opt-in README (B-0191 #2 + #3)#1586

Merged
AceHack merged 1 commit intomainfrom
feat/orchestrator-checks-pretooluse-wrapper-b-0191-aaron-2026-05-05
May 5, 2026
Merged

feat(claude-hooks): verify-branch-pretooluse.ts wrapper + opt-in README (B-0191 #2 + #3)#1586
AceHack merged 1 commit intomainfrom
feat/orchestrator-checks-pretooluse-wrapper-b-0191-aaron-2026-05-05

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 5, 2026

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.json is explicitly edited.

Per Otto-364 search-first authority

WebSearched the canonical Anthropic Claude Code hooks reference 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).

Files

Why default-off

Adding a PreToolUse hook would fire on every git commit Bash invocation -- even though it's a no-op when ZETA_EXPECTED_BRANCH is 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

B-0191 acceptance criteria status after this PR merges

# Criterion Status
1 Harness hook script ✓ (#1585)
2 Hook wired (settings.json) ✓ wrapper + opt-in instructions (this PR)
3 Documentation .claude/hooks/README.md (this PR)
4 Test ✓ (#1585 unit tests)
5 At least one mechanization sub-row landed

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings May 5, 2026 05:14
@AceHack AceHack enabled auto-merge (squash) May 5, 2026 05:14
@AceHack AceHack merged commit 9e8f449 into main May 5, 2026
27 checks passed
@AceHack AceHack deleted the feat/orchestrator-checks-pretooluse-wrapper-b-0191-aaron-2026-05-05 branch May 5, 2026 05:16
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +45 to +47
// 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts to translate verify-branch.ts exit status/stderr into Claude Code’s PreToolUse response shape.
  • Adds .claude/hooks/README.md with opt-in .claude/settings.json wiring 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.

Comment thread .claude/hooks/README.md
Comment on lines +17 to +35
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"
}
]
}
]
}
}
Comment thread .claude/hooks/README.md
Comment on lines +1 to +15
# `.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
Comment thread .claude/hooks/README.md
Comment on lines +17 to +29
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"
Comment on lines +32 to +68
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);
AceHack added a commit that referenced this pull request May 8, 2026
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>
AceHack added a commit that referenced this pull request May 8, 2026
…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>
AceHack added a commit that referenced this pull request May 9, 2026
…(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>
AceHack added a commit that referenced this pull request May 9, 2026
…(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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants