Claude Code onFailure block: fail-closed hook for paid Sume tools
Claude Code 2.1.295 adds onFailure: block for command and HTTP hooks. Use it so a crashed spend guard stops paid Sume generate calls instead of allowing them.

Set "onFailure": "block" on a PreToolUse command hook and Claude Code 2.1.295 (October 8, 2026) stops the tool call whenever that hook cannot start, times out, or exits with an unexpected code. Before this release a crashed guard simply let the call through. For paid Sume tools such as generate_video, that is the difference between a spend check that fails closed and one that silently disappears.
What changed in 2.1.295?
The changelog entry reads: added onFailure: "block" for command and HTTP hooks, so a hook that can't start, times out, or exits with an unexpected code blocks the action instead of letting it through. The hooks reference says the option needs v2.1.295 or later, defaults to continue, and that block is equivalent to exit code 2.
The old behavior matters because only exit code 2 was a blocking error. Any other non-zero exit was a non-blocking error: the action proceeded and you saw a hook error notice. A guard script with a typo, a missing interpreter, or a slow start therefore approved everything.
Which Sume calls deserve a fail-closed guard?
Sume's hosted MCP server at https://mcp.sume.com/mcp has read tools and paid or write tools. Its docs say OAuth mcp:read is read-only and mcp:write enables data-changing and paid tools, and that API-key calls need an idempotency_key for paid and write operations. The server's own run contract also asks for max_spend_usd when you want to cap a create. Those are the arguments worth checking before the call leaves your machine.
Claude Code names MCP tools mcp__<server>__<tool>. If you added the server as sume, the paid creators match mcp__sume__generate_image and mcp__sume__generate_video.
What does the hook look like?
Put this in .claude/settings.json. The 10-second timeout is well under the 600-second command default, and a timeout now blocks because of onFailure.
{
"hooks": {
"PreToolUse": [{
"matcher": "mcp__sume__(generate_image|generate_video)",
"hooks": [{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/sume_spend_guard.py\"",
"timeout": 10,
"onFailure": "block"
}]
}]
}
}The script reads the PreToolUse JSON from stdin, which carries the call under tool_input, and exits 2 with a reason on stderr when a field is missing. The except branch is deliberate: unreadable input also blocks.
import json, sys
try:
call = json.load(sys.stdin)
args = call.get("tool_input") or {}
except Exception:
sys.exit(2) # unreadable input: fail closed
missing = [k for k in ("idempotency_key", "max_spend_usd") if not args.get(k)]
if missing:
print("Sume paid call blocked, missing: " + ", ".join(missing), file=sys.stderr)
sys.exit(2)
sys.exit(0)Which hook outcomes now block?
| Hook outcome | Without onFailure block | With onFailure: block |
|---|---|---|
| Exit 0 | Call proceeds | Call proceeds |
| Exit 2 | Call blocked | Call blocked |
| Other exit code (for example a Python traceback exits 1) | Call proceeds, hook error notice | Call blocked |
| Cannot start or times out | Call proceeds | Call blocked |
What should you still not rely on?
A hook is a local convenience, not a wallet. Sume's own admission and wallet controls decide whether a job is accepted, and a client-side check cannot raise or lower that. Keep idempotency_key unique per intended job, because a retry of the same key is how a repeated call avoids a second charge.
If you would rather be asked than blocked, an ask permission rule on the paid tools is the lighter option; the ask-rule post covers it, and the dry-run hook post shows a hook that previews cost first.
Sources
Related posts
More in Integrations
- Claude Code MCP behind a proxy: 403 now fails fast, allow Sume's host
Claude Code 2.1.290 and 2.1.292 stop retrying an MCP server your proxy blocks with 403. What to ask your proxy admin to allow so Sume's hosted MCP connects.
- How to add an MCP server to ChatGPT with developer mode
Turn on ChatGPT developer mode, create an app for the server's URL, and sign in with OAuth. The steps, with Sume's hosted MCP server as the example.
- How to add subtitles to a video in Python
Add subtitles to a video in Python with Requests: POST the video URL to Sume's /v1/video-captions, poll the job, then read the captioned video_url.
- Add Sume to Claude as a custom connector (remote MCP)
Add Sume's hosted MCP server to Claude under Customize > Connectors, see what Sume's OAuth consent grants, and decide whether to allow paid tools.
Written by Sume