The hook system — shipped scripts that Claude Code runs on session and tool lifecycle events
Failure modes
| Symptom | Cause | Fix | Severity |
|---|---|---|---|
| Hook blocks a real tool on odd input | script raised / exited non-zero on malformed stdin | parse defensively and exit 0 on any non-dict / non-JSON payload |
high |
| Tool not blocked when it should be | wrong exit code (only 2 blocks a PreToolUse) |
sys.exit(2) to block |
high |
| Banner or side effect missing | script exceeded its hooks.json timeout and was killed |
keep the script fast; move slow work off the critical path | medium |
| Hook never fires | event not wired in hooks.json, or wrong event name |
check the mapping and the Claude Code event name | medium |
Risk areas
- Fail open. A
PreToolUseguard must exit0on malformed input, never crash — a crashing guard silently stops blocking. - Only
2blocks. Any other exit code from aPreToolUsehook lets the tool through. - Timeouts are real. A script slower than its
hooks.jsontimeout is killed and its effect is lost.
Diagnosis order
- Is the event wired to the script in
hooks.json? - What exit code does the script return for this payload?
- Does it fail open on malformed stdin?
- Is it finishing inside its timeout?