Claude Code hook: exit 2 or a JSON deny for a paid Sume call?
Exit code 2 blocks and cannot be overridden by JSON; exit 0 with permissionDecision deny carries a reason. A runnable Python guard for Sume's paid tools.

Prefer exit 0 with a JSON permissionDecision of deny when you block a paid Sume call. The hooks reference says exit code 2 is a blocking error that no JSON can override, while exit 0 lets the JSON fields decide and carries a permissionDecisionReason that Claude can act on. Use exit 2 only for a hard stop.
Below is a guard for the paid tools that denies any call missing an idempotency_key, which Sume requires on write and paid tools anyway.
What each exit path does
Both come from the hooks reference.
| Exit | JSON honored | Use for |
|---|---|---|
| 0 | Yes | Decisions with a reason, such as deny, ask, allow or defer |
| 1 to 5 (non-blocking) | If valid | Errors that should not stop the call |
| 2 | No, cannot be overridden | Hard blocking errors |
| Other | If valid | Treated as non-blocking |
The guard
Save as guard_sume.py and call it from a PreToolUse hook whose matcher lists the paid names exactly. It reads the hook input from stdin, so it runs as a normal script.
import json
import sys
def main() -> None:
data = json.load(sys.stdin)
args = data.get("tool_input") or {}
if args.get("idempotency_key"):
return
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
"Paid Sume tools need a stable idempotency_key. "
"Retry with one."
),
}
}))
main()Wire it up and test
Register it under hooks.PreToolUse with a command such as python3 guard_sume.py and the matcher mcp__sume__generate_image|mcp__sume__generate_video. Ask the model to render something and watch the reason text come back. Because the hook exits 0, Claude sees the reason and can retry with a key. Then try dry_run=true, which Sume documents as an admission and cost preview that does not submit a job.
- A key that changes on every retry defeats deduplication; generate it once per intended job.
- Add a second check for
max_spend_usdif your policy needs a cap. - Run
claude --debug-file hooks.logto see hook execution if a deny does not appear.
Choosing between deny, ask, defer and allow
The reference lists four PreToolUse decisions: deny, allow, ask and defer. For a paid Sume call, a deny with a reason suits a call that is malformed, such as a missing key. An ask suits a call that is well formed but expensive, because it hands the decision to a person. An allow can skip the normal permission prompt, so use it only for read tools such as balance_get or jobs_list if you want them frictionless.
The hook may also return updatedInput to change the arguments before the tool runs. Be careful using that on a Sume call: silently adding or changing an idempotency_key or a spend cap changes what the user thinks was submitted. Prefer a visible deny and let the model resend.
What Sume does not do
The key is a transport and dedup value, not human approval, per Sume's docs. A hook that checks for its presence stops sloppy calls but does not ask a person. Use an ask decision if you want a prompt, and remember that Sume enforces max_spend_usd only when the caller provides it.
Sources
Related posts
More in Developers
- Claude Code http hook: send paid Sume call records to an audit log
An http hook POSTs hook input to a URL, with headers from allowed environment variables only. Log Sume tool names and idempotency keys with a tiny receiver.
- Claude per-message effort: drop to low while a Sume job runs
Claude's per-message effort beta keeps the prompt cache when you change effort mid-conversation. How to use it around Sume jobs, and its Haiku 5.5 limit.
- Hook matcher for a plugin-bundled Sume server: mcp__plugin_ names
A Sume server shipped inside a Claude Code plugin gets a longer tool prefix than one added by hand. Write the PreToolUse matcher for it and test it first.
- Where a Claude Code plugin keeps Sume state: CLAUDE_PLUGIN_DATA
A plugin's data directory survives updates, while its root changes. Where to put a Sume call log or cache, and when uninstalling deletes it.
Written by Sume