Field note · No. 023 · November 7, 2026
Agent SDK hooks can now fail closed, so make it your default
Claude Code 2.1.317 lets a failing Agent SDK hook block the action it guards instead of waving it through. A guard that disappears when it crashes was never a guard, and this changes how policy hooks should be written and tested.
What changed
Claude Code 2.1.317 added onFailure: "block" for Agent SDK hook callbacks, so a callback that fails blocks the action instead of letting it run. Before this, a broken policy or guard hook could let the tool call go through. The release is summarised as shipping new security checks, stricter Agent SDK hook controls, and richer usage and cost reporting. It was first seen on 3 November 2026.
Two related Agent SDK fixes appear nearby, with the exact version unconfirmed: - Custom slash commands that fail no longer print nothing in claude -p and SDK sessions. - claude -p and Agent SDK sessions no longer report success when an API error cuts off a reply.
A note on sourcing: I read these through Releasebot, which aggregates Anthropic's release notes. I would confirm the option name and version against Anthropic's own changelog before depending on them.
Why it matters for builders
A hook that guards a tool call is a security control, and a control that fails open is only present while everything is normal. Hooks fail for ordinary reasons: an unhandled exception, input shaped differently than expected, a policy lookup that cannot reach its backend. Those are exactly the moments when something unusual is happening, which is when you most want the guard to hold.
Failing open is also silent. The tool call runs, the session continues, and nothing in the transcript says the check never happened. The two nearby fixes share the same theme: a failed command that prints nothing, and a truncated reply reported as success. All three are cases where a failure looked like a success to the caller. For anyone running agents headless in CI or a backend job, that gap is where incidents hide.
Failing closed has a cost. A buggy hook now stops legitimate work, so the hook's own reliability becomes an availability concern. That is the right trade for anything that writes, executes or sends data out, but it means hooks deserve the same tests and review as the code they protect.
What I would do with it
I would start by sorting hooks into two groups: guards and observers. Guards sit in front of side effects, such as file writes, shell commands and outbound network calls. Observers log or measure. Guards get onFailure: "block". Observers can stay permissive, since blocking an action because a metrics call failed protects nothing.
Then I would make the failure distinguishable. A block caused by a crashed guard and a block caused by a deliberate deny are different events. The message the agent sees, and the log a human reads, should say which one happened, so nobody spends an afternoon loosening a policy that was never the problem.
Next, test the crash path as deliberately as the deny path: - A guard that throws should leave the tool call unexecuted. - A guard that receives malformed input should block, not pass. - A guard whose dependency is unreachable should block and say so.
Finally, for headless runs, I would stop treating any output as success. Check the session's reported result and exit status, and treat a cut-off reply as a failed step. Pin the Claude Code version in CI so the fail-closed behaviour you tested is the behaviour you run.
A guard that lets the action through when its own check crashes is not a guard, so make every hook in front of a side effect fail closed and test the crash path as carefully as the deny path.