Summary
Both tools/branch-guard/hook.sh and tools/file-guard/hook.sh emit hookSpecificOutput JSON without the required hookEventName field. Claude Code rejects the payload with:
Hook JSON output validation failed — hookSpecificOutput is missing required field "hookEventName"
When validation fails, the harness falls back to allow — so a deny decision from either guard is silently ignored, defeating the protection the hook is meant to provide.
Repro
On a protected branch (e.g. main with branch-guard's defaults):
echo '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}' \
| bash tools/branch-guard/hook.sh
# emits: {"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}
Run inside Claude Code: the validation error surfaces in the UI, but the git commit proceeds.
Affected lines
tools/branch-guard/hook.sh:68 — 1 site (the block() helper)
tools/file-guard/hook.sh — 13 sites (lines 65, 318, 323, 332, 337, 362, 367, 391, 396, 419, 424, 445, 453, 472)
Required shape
Per Claude Code's hooks reference, hookSpecificOutput must include hookEventName as a discriminator:
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}
Fix
Single substring replacement in both files:
'{"hookSpecificOutput":{"permissionDecision":"deny"
→
'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny"
Happy to open a PR if useful.
Summary
Both
tools/branch-guard/hook.shandtools/file-guard/hook.shemithookSpecificOutputJSON without the requiredhookEventNamefield. Claude Code rejects the payload with:When validation fails, the harness falls back to allow — so a
denydecision from either guard is silently ignored, defeating the protection the hook is meant to provide.Repro
On a protected branch (e.g.
mainwith branch-guard's defaults):Run inside Claude Code: the validation error surfaces in the UI, but the
git commitproceeds.Affected lines
tools/branch-guard/hook.sh:68— 1 site (theblock()helper)tools/file-guard/hook.sh— 13 sites (lines 65, 318, 323, 332, 337, 362, 367, 391, 396, 419, 424, 445, 453, 472)Required shape
Per Claude Code's hooks reference,
hookSpecificOutputmust includehookEventNameas a discriminator:{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}Fix
Single substring replacement in both files:
→
Happy to open a PR if useful.