Claude Code v2.1.317 landed on November 3, three days after 2.1.316. Most release notes are feature lists. This one reads like an audit response. Almost every added line is about what happens when a guardrail fails, and most of the fixes either make a quiet failure loud or make an unsafe default safe. I went through the changelog and the hooks reference and pulled out the changes that touch how I write agent configs.
Hooks still fail open by default
The ground rules first, because they explain why this release matters. A command hook answers with an exit code. Exit 0 is success. Exit 2 blocks the action. Anything else is a non-blocking error, and the action runs anyway. The hooks reference states the consequence plainly: "On most events, when a hook fails or times out, Claude Code still carries out the action. A policy hook with a wrong path or a crashing script then lets everything through."
That is fail-open by design. If your PreToolUse guard script has a typo in its path, the hook never starts, the tool call proceeds, and you get a one-line notice in the transcript. A timed-out hook is the same story. You cannot count on a stalled guard to act as a gate.
2.1.317 brings onFailure block to SDK callbacks
Command and http hooks in settings.json have had an opt-in since v2.1.295: "onFailure": "block" turns any hook failure into what exit 2 does on that event. What is new in 2.1.317 is the same switch for Agent SDK hook callbacks, the in-process hooks you register when you embed Claude Code in your own tooling. The changelog line reads: "Added onFailure: "block" for Agent SDK hook callbacks, so a callback that fails blocks the action instead of letting it run."
The settings.json shape, for reference:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
"onFailure": "block"
}
]
}
]
}
}
Two details from the reference worth remembering. The failure list is broader than a crash: a hook that cannot start, an exit code other than 0 or 2, an HTTP error, a timeout, and invalid JSON output all count as failures. And the switch has no effect on Stop, SubagentStop, TaskCompleted, and TeammateIdle hooks, or on async hooks. If your guard lives on one of those events, onFailure will not save you.
The docs even give the test procedure: delete the script, ask for a harmless command, and expect the block message. If you run policy hooks and have never done that test, this release is a good excuse.
A tool call that leaks your email now asks first
One added line with real weight: "Added a check that asks before a web or shell tool call sends your account email address to the network."
Picture the attack shape. A prompt injection in a README or a test file tells the agent to curl a webhook with environment details. The account email is a stable identifier that crosses org boundaries, and it costs an attacker nothing to collect. Until now, a web or shell tool call could carry it out with no consent step. A consent prompt at the network boundary is cheaper than rotating an identity later. This is the same class of thinking as the marketplace malware problem I wrote about on October 30: the config layer decides what leaves the machine, so the defaults matter more than the intent.
/schedule warns about connectors you did not ask for
Cloud scheduled tasks can end up with connectors attached that you never requested. 2.1.317 adds a warning to /schedule when that happens, along with how to remove them.
This is scope creep made visible. A scheduled task that quietly talks to an extra connector is a data path nobody approved, running on a schedule nobody watches. If you use cloud tasks, check the warning on your next /schedule run even if you think your tasks are minimal.
A full disk used to exit 0
This is the fix I care about most, because it lives in CI. Before 2.1.317, claude -p could exit 0 when a full disk or a quota lost its transcript or a ~/.claude.json write. Your pipeline would read success on a run whose transcript was gone. The fix prints an error, and in text and json output modes exits 1.
Exit 0 on a lost artifact is the worst kind of lie a CLI can tell. If you gate merges or deploys on claude -p exit codes, the codes just became worth trusting. Keep asserting on stderr too, since a nonzero exit tells you it failed, not why.
Settings edits in the first second were silently dropped
One fix that smells like a debugging marathon: "Fixed edits to settings files made in the first second or so after launch not taking effect until a settings file changed again."
If you script config changes right after launching a session, a guard you added in that window never applied, and nothing told you. The config was on disk but not in the session. Anyone who applies hook or permission changes at session start should re-verify their setup actually loaded, once, on 2.1.317.
Governance fields and a cost report API
For anyone running Claude Code behind a gateway, two additions push in the same direction. OpenTelemetry metrics, events, and spans now carry anthropic.plan, anthropic.seat_type, anthropic.provider, and anthropic.custom_base_url, so you can split spend and behavior by routing path instead of guessing which gateway a span came from. And the gateway admin API gained GET /v1/organizations/analytics/cost, which reports past spend over a date range in total, per developer, per day, or per IdP group.
One behavior change to note rather than fix: subagents and teammates on a different model from the main conversation now use that model's saved effort level or its default, instead of the session's effort level. If you pinned effort globally and assumed subagents inherited it, check your setups.
What I changed in my configs
After reading this changelog, three things moved onto my checklist:
- Every policy hook gets
onFailure: "block"and the deletion test. A guard that fails open is a guard you do not have. - Guards exit 2 with a reason on stderr, never exit 1. Exit 1 on a policy hook is a non-blocking error, which means the action runs.
- CI asserts on
claude -pexit codes and treats any transcript loss as a hard failure, now that the exit code is honest.
The theme of this release is that failure is a config surface. Hooks, tool calls, scheduled tasks, and CLI exits all have a failure behavior, and in every case this version either asks you first or fails toward safety. The MCP tasks changes from October 26 pushed config decisions out to long-running tool calls. This one pushes them inward, to what happens when the machinery breaks. Both directions need the same thing from us: defaults written down, tested, and not assumed.
Top comments (0)