How do I make Claude Code hooks fail closed with `onFailure: "block"`?
Claude Code 2.1.295 adds onFailure: "block" for command and HTTP hooks: a hook that can't start, times out, or exits oddly blocks the action. Docs lag.
From Claude Code 2.1.295, command and HTTP hooks accept onFailure: "block". With it, a hook that can't start, times out, or exits with an unexpected code blocks the action instead of letting it through. That wording comes from the CHANGELOG. As of 2026-10-09 ~08:40 CEST, the official hooks reference does not mention onFailure yet, so treat placement details below as inferred.
Why you need it: hooks fail open by default
The hooks reference is explicit about the default (checked 2026-10-09):
- For most events, exit code 2 is the only exit code that blocks on its own. Exit code 1 without valid JSON is a non-blocking error and the action proceeds.
- A hook that can't start (missing or non-executable script, shell exit 127) lands in the same non-blocking bucket. The docs warn that "a mistyped path in
settings.jsonleaves the gate silently disabled." - On
PreToolUse, a timed-out command hook lets the tool call continue. Default timeout forcommandandhttphooks is 600 seconds on most events.
So a policy hook that crashes, hangs, or was never installed correctly lets everything through. onFailure: "block" flips that for command and HTTP hooks.
What 2.1.295 added
Added
onFailure: "block"for command and HTTP hooks: a hook that can't start, times out, or exits with an unexpected code blocks the action instead of letting it through
Scope from that bullet: type: "command" and type: "http" only. Nothing is said about prompt, agent, or mcp_tool hooks, so don't assume it works there.
How to set it
onFailure is a per-hook field, next to type and timeout. [Inferred] placement, since docs don't show it yet:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh",
"timeout": 30,
"onFailure": "block"
}
]
}
]
}
}
Then:
- Upgrade to ≥ 2.1.295 (
claude --version). - Keep your intentional block on
exit 2.onFailurecovers the unexpected failures; it doesn't replace your policy logic. - Set a short, explicit
timeout. With fail-closed, a hung hook now blocks, so 600 s of waiting is a long stall. - Test the failure path: point
commandat a path that doesn't exist and confirm the tool call is blocked, not run with a non-blocking notice.
For org-wide enforcement, ship the hook from managed settings so a repo can't drop it.
Docs lag: what to trust
| Claim | Source | Status 2026-10-09 |
|---|---|---|
onFailure: "block" exists, command + HTTP | CHANGELOG 2.1.295 | Verified |
| Default is fail-open (exit 1, can't start, PreToolUse timeout) | Hooks reference | Verified |
| Field sits on each hook entry | Our inference | [Inferred], recheck when docs update |
Other onFailure values | — | [GAP], not documented |
Behavior on non-blockable events (e.g. SessionStart) | — | [GAP] |
Pitfalls
- Exit 1 as policy. Still non-blocking unless the hook fails in a way
onFailurecatches. Useexit 2for "deny." - Long timeouts + fail-closed. Every slow run becomes a block after the full wait.
- Async hooks. The docs say Claude Code doesn't enforce
timeoutonasync: truecommand hooks; don't count on fail-closed for those. - Mods. 2.1.295 also fixed a mod hook receiving deeply nested tool input cut short with no error. Upgrade if you guard with mods.
FAQ
Does onFailure: "block" change what exit 2 does?
No. Exit 2 already blocks on blockable events. onFailure covers can't-start, timeout, and unexpected exit codes.
Which version do I need? 2.1.295 or later. See our 2.1.295 notes.
Is this related to retry settings? No. Retry waits on 429/529 are separate; see the overloaded retry Spec.
Sources (checked 2026-10-09 ~08:40 CEST)
- Claude Code CHANGELOG.md: 2.1.295
onFailurebullet and mod-hook fix. The CHANGELOG doesn't date versions, so the release day is [GAP]. - Hooks reference: exit-code behavior, can't-start bucket, timeouts and defaults, async hooks. No
onFailureentry on this check.
