I already have a PreToolUse hook that returns exit 2. Do I still need onFailure?
You should consider it. Exit 2 only takes effect when the script actually runs and deliberately returns 2. If the path is mistyped, the file lacks execute permission, it times out, or the script crashes midway and exits 1, the guard never "ruled" on anything and the default is to let the action through. onFailure: "Block" covers exactly the case where the guard did not show up for work, so the two are complementary.
How do I verify that my hook really fails closed?
Test in a throwaway directory with claude -p. Do three things separately: break the script path, add a sleep so it exceeds the timeout, and make the script exit 1 outright. For each, ask Claude to run a harmless command that should be blocked, confirm it gets blocked, then restore the configuration and confirm a safe command still passes. The Developers Digest test follows this idea and changes only the hook as a variable.
Can prompt hooks and agent hooks use onFailure too?
Per the Developers Digest summary, not currently: onFailure applies only to command and HTTP hooks. For rules that need a hard stop, use a command hook as the guard and make it fail closed as described above; prompt and agent hooks are better suited to soft checks that need semantic judgment. v2.1.294 also notes that these two kinds, when written as instructions, previously let through actions they should have blocked, which is a sign they are a poor choice as the only line of defense.
I'm not an engineer; I only look after the team's rules. How does this concern me?
If your team relies on hooks to enforce rules (for example no deleting files, no pushing straight to main), you can ask the engineers one simple question: "What happens when the guard script breaks or times out?" The default answer is that the action goes through, with only an easily missed error line on screen. Check whether anyone has set onFailure: "Block" for the critical rules, and whether someone periodically verifies that the gate still works.
Many people treat a hook as a security gate that "guarantees a Block," but the official hooks documentation is explicit: only exit code 2 blocks an action by itself. Any other non-zero exit does not block; it just shows a hook error line in the transcript. That means that when a guard script breaks, the default outcome is to let the action through. v2.1.295, released October 8, 2026, adds onFailure: "block" to close exactly this hole.
According to the official hooks documentation, none of the following blocks a PreToolUse call:
The release notes describe it this way: command and HTTP hooks can be set to block the action, instead of letting it through, when the hook cannot start, times out, or exits with an unexpected code. A test published by Developers Digest on October 9 ran six configurations through claude -p on v2.1.295, changing only the PreToolUse hook: with nothing set, both a missing script and a timeout let the command run; with "onFailure": "block" both were blocked, while a working guard still allowed safe commands.
That write-up also states the scope: it matters only for events that can block, such as PreToolUse, UserPromptSubmit and Stop, and prompt, agent and MCP tool hooks are not covered. The configuration below follows that article's example (the page of the official hooks documentation I could read does not show this key, so confirm with the official docs or claude Plugin validate before relying on it):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard.sh",
"timeout": 5,
"onFailure": "block"
}
]
}
]
}
}
onFailure alone is not enough. If a script finishes but exits 0 because some command inside it failed, Claude Code sees a successful allow. The Developers Digest author found that a guard without error handling, on a machine without jq, exited 0, which would have let rm -rf through. The sturdier pattern is to make any unexpected error exit 2. Below is a sketch written on that principle (the matching rule is deliberately crude and only shows the flow):
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "guard crashed" >&2; exit 2' ERR
cmd=$(jq -r '.tool_input.command')
if printf '%s' "$cmd" | grep -Eq 'rm +-rf +/'; then
echo "blocked: destructive rm" >&2
exit 2
fi
Here trap ... ERR turns a failure such as a missing jq into exit 2, while a grep inside an if condition that finds no match returns 1 without firing the trap.
Failing closed has a cost: if the HTTP endpoint your guard depends on goes down, every Bash call is blocked, and a convenience hook such as a formatter or notifier does not need to stall a whole session when it breaks. The following is my recommendation, an inference rather than official policy:
onFailure: "block" only for irreversible or abusable actions (deletes, pushes, credential exfiltration).v2.1.294 (October 8) fixed prompt and agent hooks written as instructions (for example "Block commands that…") letting through what they should block, and improved how prompt hooks on Stop and SubagentStop are judged so Claude is less likely to stop early. A summary of the earlier v2.1.288 notes also mentions that a PreToolUse hook skipped because of a matching failure now blocks the call (see the original notes for details). With several "the gate didn't hold" fixes landing within one month, it is worth re-testing your own hooks.