Claude Code 2.1.295 Adds onFailure block: Hooks Can Fail Closed Now, If You Ask
Claude Code shipped two releases in one day this week. Version 2.1.294 landed at 03:42 UTC on October 8 as a hotfix for instruction-style hooks that allowed what they should block, which I walked through yesterday. Versi
Claude Code shipped two releases in one day this week. Version 2.1.294 landed at 03:42 UTC on October 8 as a hotfix for instruction-style hooks that allowed what they should block, which I walked through yesterday. Version 2.1.295 followed at 19:48 UTC the same day, a big stability release, 143 changes by one tracker's count, 89 of them fixes. One line in the Added section changes the failure model of hooks themselves: onFailure: "block". A hook that cannot start, times out, or exits with an unexpected code now blocks the action instead of letting it through. If you write guard hooks for a living codebase, that is the headline. The catch: it is opt-in, per hook, and the default is still fail-open.
What the changelog says
Verbatim, from the official changelog under 2.1.295:
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
Read that as three separate failure paths that all used to end the same way. The script is missing or not executable. The script runs past its timeout. The script exits with a code nobody expected. In every one of those cases, the action proceeded. A guard that does not run is not a guard, and until this release there was no way to say that in config.
The default is still fail-open
Upgrading changes nothing about your existing hooks. If a command hook fails to start tomorrow, the tool call goes through exactly as it has all along. The new behavior exists only where you ask for it, on the hook entry, next to type and command:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./guards/block-pattern.sh",
"onFailure": "block"
}
]
}
]
}
}
That one property is the whole change, and it is easy to miss in a 143-item list. Nothing prompts you to add it. Nothing flags a hook that lacks it.
Why opt-in is defensible, and where it bites
Fail-closed sounds like the obvious default. It is not free. Plenty of hooks are advisory: format nudges, lint reminders, log lines. If hook failure meant block by default, a CI box with a stale virtualenv would freeze every session the moment a black --check wrapper stopped launching. Anthropic chose the flag over the flip, which keeps advisory hooks harmless and gives load-bearing ones a way to declare themselves.
The decision you now make per hook is the same one I landed on when a 7-line mod overrode a deny rule: is this hook a preference or a prohibition? Preferences can fail open. Prohibitions get onFailure: "block".
Two more guard leaks closed in the same release
Buried in the 89 fixes are two that read like continuations of yesterday's story:
Fixed a mod's hook being handed a deeply nested tool input cut short with no error, so a guard could pass content it never saw
Fixed calls a mod makes while it reloads during a plugin hooks worker restart getting past another mod's guard hook that has a
.catch; such calls are now refused
The first means a guard hook could receive a truncated view of a deeply nested tool input and approve it, silently, because the truncation produced no error. The second means a window existed during plugin reload where calls skipped a guard hook entirely. Neither printed a warning. Both belong to the same class as 2.1.294's fail-open instruction hooks: config that looks active and, in certain states, is not.
A third change attacks the problem from the install side: claude plugin install, enable, disable and marketplace add now warn when the settings file they write to does not load. That is the first time the tool tells you that a guard you believe you installed is sitting in a file the runtime never reads.
The 5-minute probe
After upgrading, prove that fail-closed actually fails closed on your setup. Pick one guarded action you can trigger safely, then break the hook on purpose:
mv .claude/guards/block-pattern.sh .claude/guards/block-pattern.sh.off
Trigger the action. Expected result on 2.1.295 with onFailure: "block": blocked, with a reason naming the failed hook. Without the property: the action runs, which is the old behavior and worth seeing once. Restore the file and trigger again to confirm the guard passes on merit. That pair of runs is your only real evidence, because a fail-open hook and a healthy hook produce identical output when everything works. It is the same probe-first discipline behind the scanner we ship for silent config failures: the file on disk is the claim, the behavior is the evidence.
What onFailure does not cover
The property applies to command and HTTP hooks. Instruction-style prompt and agent hooks, the prose kind that 2.1.294 patched, are still judged by a model, and a sentence has no exit code to fail. If your guardrail is prose, this release does not harden it. So the rule from yesterday stands, now with a sharper edge: prohibitions belong in scripts with exit codes, prose belongs to preferences. What changed is that the script route finally closes on failure instead of only on match. This moving surface is also why I keep agent configs version-pinned with a recorded changelog ($29): when the client shifts underneath your rules, you want to know what changed and when, not discover a missing guard after the fact.
Thirty seconds on the rest of 2.1.295
OSC 7501 support means terminals implementing the Program Status Protocol can show whether Claude Code is working, waiting on you, or done, which matters if you watch sessions from scripts instead of window titles. CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS caps how long unattended retry mode waits out 429 and 529 errors, a lever for overnight batch runs. Remote MCP servers that drop connections now back off up to 30 seconds instead of reconnecting in a tight loop, and headless sessions no longer stay disconnected after an outage longer than 15 seconds. Small items, all of them aimed at the same target as the headline: sessions that degrade loudly instead of quietly.
The short version
Upgrade, then earn it. Add onFailure: "block" to every hook whose job is to prohibit, and run the broken-hook probe once so you have seen both failure modes with your own eyes. The property is one line per hook. The audit is five minutes. The alternative is a guard that only works on the days it happens to run, which is the exact failure class this week's three releases have been cleaning up.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.