Claude Code hooks can now fail closed with onFailure
Claude Code 2.1.295 (October 8, 2026) adds an onFailure field to command and http hooks. It lets a policy hook fail closed.
The old default fails open
Until now, a hook that broke did not stop anything. A hook that exits with any code other than 0 or 2, fails to start, or prints invalid JSON is a non-blocking error. The action goes ahead, and you see a <hook name> hook error notice in the transcript.
A timeout behaves the same way on PreToolUse. A timed-out command, http, or mcp_tool hook does not block the tool call. The call continues through the normal permission flow.
So a policy hook with a wrong script path, or one that crashes, lets everything through.
The field
onFailure takes "continue" (the default) or "block". This PreToolUse hook in .claude/settings.json blocks a Bash command if its script fails:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
"onFailure": "block"
}
]
}
]
}
}
To test it, leave check-command.js missing and ask Claude to run ls. The call is blocked, and the error includes failed; blocking because onFailure is "block". After a timeout, the message says timed out instead of failed. Without the field, the same missing script is a non-blocking error and ls runs.
What counts as a failure
- A command hook that can't start, such as a missing script or executable.
- A command hook that exits with a code other than 0 or 2. This holds even if it printed JSON that allows the action, such as
permissionDecision: "allow". To return a JSON decision, exit 0. - An HTTP hook whose connection fails or whose response status isn't 2xx.
- A hook that reaches its
timeout.
Prompts too
The same field applies to UserPromptSubmit. A command, HTTP, or MCP tool hook there that reaches its timeout is canceled, and its output, including any additionalContext, is discarded. The prompt still reaches Claude without that context. Set onFailure: "block" on a command or HTTP hook to block the prompt instead.
What to do
If a hook is a gate rather than a convenience, add "onFailure": "block". Then run it once with the script missing and confirm the action is refused. Leave advisory hooks, such as formatters and loggers, on the default.