How is onFailure: "Block" different from exiting with code 2 inside the script?
Exit 2 is the script declaring, after running normally, that the action is not allowed, and it assumes the script actually started and reached that line. onFailure: "block" covers the case where the script never ran, timed out or exited unexpectedly, meaning the checker itself failed.
They complement each other: exit 2 expresses the verdict, and onFailure makes sure that when the checker falls over, the default is not to let the action through.
Should every hook get onFailure: "Block"?
No. For helper hooks such as notifications, logging and formatting, carrying on when they break is usually better than stalling everything, and fail-closed turns those small problems into work stoppages.
Turn it on only for gates where a missed block costs more than a false block, such as the few guarding production, payment commands and customer data.
How do I confirm my gate hook really blocks?
The most direct way is a deliberately broken test: point the script path at something that does not exist, or make it sleep past its timeout, then trigger the action it guards and see whether the action is actually blocked.
You can also use claude Plugin validate for hook error-handling advice, but a real test is the only way to prove the gate holds when it fails.
Can I find this option in the official docs, and should I use it now?
When I checked, the readable part of the official hooks page did not yet list it, and the semantics come only from a one-line changelog entry. That means the exact field location and applicable events are yours to verify.
Test it first in a scratch project with a hook that fails on purpose, and roll it into shared team settings once the documentation catches up.
Claude Code v2.1.295 (October 8, 2026) adds onFailure: "Block" for command and HTTP hooks. The changelog describes it as blocking the action when a hook cannot start, times out or exits unexpectedly. To see what it fixes, start with the old default behavior.
The official hooks documentation is explicit: for most events, when a script path does not exist or is not executable, the shell exits with a code such as 127, Claude Code shows a non-blocking notice, and the action proceeds. The docs even warn that when you set up a policy hook, a mistyped path in settings.json leaves the gate silently disabled. Timeouts work the same way: a timed-out command, http or mcp_tool hook on PreToolUse does not block the tool call, and the docs say not to count on a stalled hook as a gate. Non-2xx HTTP responses and connection failures are also non-blocking errors. Even exit code 1, the conventional Unix failure code, is non-blocking without valid JSON; only exit 2 blocks.
In other words, policy hooks used to be fail-open: if the checker itself broke, there was effectively no check. onFailure: "block" lets you choose fail-closed per hook: if it cannot start, times out or exits unexpectedly, the action is blocked instead of continuing through the normal permission flow. That matters most for gates where a false block is acceptable and a missed block is not, such as hooks that guard certain directories, deployment commands, or call an external compliance API for approval. One caveat: the part of the official hooks page I could read does not yet list this option, so the semantics above come from the one-line changelog entry. Which settings field it goes in, which events it applies to and how it interacts with timeout should be confirmed against updated official docs, and you should verify it yourself with a deliberately mistyped hook before relying on it.
The cost of fail-closed is availability: if the compliance service behind an HTTP hook goes down, every action it guards is blocked. Turn it on only for genuinely high-risk gates, handle predictable errors inside the hook script, and keep an explicit escape hatch such as a managed-settings switch that can disable the hook quickly. Also, v2.1.295 adds advice from claude Plugin validate on hooks, and v2.1.290 already lists whether each gating hook has a .catch; together they help you find gates with no error handling before launch.
If your team uses hooks to guard things that must not be touched, such as production credentials, payment-related commands or customer data directories, audit them today: which hooks would turn into silent pass-throughs if they broke? List them, add onFailure: "block" to the highest-risk few, and write a test that fails on purpose. Discovering two months later that a gate had been dead all along costs far more than ten minutes of checking.