What Exit Code 2 Does to a Hook's Tool Call
A team writes a PreToolUse hook meant to block any Bash call
containing rm -rf. During testing, a bug in the script's exit-code
handling causes it to exit with status code 2 on a totally harmless
command that doesn't match the dangerous pattern — and the script
prints nothing to stdout.
Given how Claude Code interprets hook exit codes, what happens to that harmless tool call?
The correct answer is "It's blocked — exit 2 is a blocking error unless overridden."
Claude Code treats a hook's exit code as its decision mechanism: exit
code 0 means proceed (optionally paired with JSON output expressing
a decision), while exit code 2 is a blocking error — the tool call
is stopped, unless the hook's JSON output explicitly overrides that
outcome. Because the buggy script exits 2 and emits no JSON output
to override it, the harmless command gets blocked exactly as if it had
matched the dangerous pattern — a real, and instructive, consequence
of the bug rather than a no-op.
The distractors don't match this behavior: exit codes 0 and 2 are not equivalent — one proceeds, the other blocks by default; Claude Code does consult the exit code itself (JSON on stdout refines the decision, it doesn't replace the exit code as the primary signal); and nothing in the hook mechanism auto-disables a hook after an unexpected result — a misbehaving hook keeps firing on every matching call until someone fixes or removes it.
Share this question