If you’re building something that connects to a paid API, it’s surprisingly easy for your coding harness to start spending your app’s API credits instead of its own.
While working on a batch task on my Crossword Generator, Claude wrote and ran a script that called the Anthropic API using my production key. This is meant for end users. It burned $7 dollars before I caught it. I’m lucky because I just happened to be monitoring my API usage actively to see how much spend is happening on my Google Font Identify project.
I have a spending limit set at $50 already so it would have maxed out there, but I want to prevent this from happening entirely.

How to prevent Claude from Clauding?
There are some obvious solutions to prevent Claude from spending on your API key, but they don’t go far enough (and I imagine you’ve already considered them):
- Tell Claude not to do this in CLAUDE.md, which of course it may or may not read. This is the weakest possible control — it only works if the agent reads it and honors it.
- You can set up spend limits and email alerts in Claude Console. (This is fine, but it just limits the damage, rather than prevents Claude from spending on your Antrhopic API key entirely)
- Purge the env variable from your local environment. This solution works, but now you can’t test any of the AI features on your site.
My solution
Claude Code supports hooks — commands the harness runs at defined points, enforced by the harness itself rather than by the model’s good behavior. A PreToolUse hook runs before every tool call and can deny it.
The key insight: a hook fires on the agent’s tool calls, not on commands you type in your own terminal. The agent gets blocked, but my own local testing with the real key still works. And put it in user-level settings and it applies to every repo, current and future — set up once.
Set it up
(Reading this in Claude Code? Point the agent at this post and ask it to set this up — the steps below are all it needs. It should merge into your existing ~/.claude/settings.json, not overwrite it.)
1. Create the guard script at ~/.claude/hooks/block-anthropic-spend.sh:
#!/usr/bin/env bash
set -u
cmd=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$cmd" ] && exit 0
# Case-insensitive patterns that signal an actual paid Anthropic call.
# Chosen to catch real spend without firing on innocent mentions
# (installing the SDK, grepping code, editing files).
PATTERNS=(
'api.anthropic.com/v1/' # direct REST call
'sk-ant-' # a live key in a command
'anthropic.*messages.(create|stream)' # inline SDK call (either order)
'messages.(create|stream).*anthropic'
# add your own project's spend scripts / endpoints here, e.g.:
# 'my_batch_generate.py'
)
for pat in "${PATTERNS[@]}"; do
if printf '%s' "$cmd" | grep -iqE "$pat"; then
reason="BLOCKED: this command looks like it would spend an Anthropic API key (matched /$pat/). Do the LLM work in-session instead, or run it yourself in a terminal (where this guard does not apply). False positive? Edit ~/.claude/hooks/block-anthropic-spend.sh."
jq -cn --arg r "$reason"
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'
exit 0
fi
done
exit 02. Make it executable:
chmod +x ~/.claude/hooks/block-anthropic-spend.sh3. Register it in ~/.claude/settings.json. Merge this in — if you already have a PreToolUse hook with a Bash matcher, add the new command as another entry in that matcher’s hooks array rather than creating a second block:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/block-anthropic-spend.sh"
}
]
}
]
}
}4. Verify it works. Confirm the JSON is valid and the guard denies a matching command while letting normal ones through:
jq -e . ~/.claude/settings.json >/dev/null && echo "settings JSON valid"
# Should print a "deny" decision:
echo '{"tool_input":{"command":"echo sk-ant-test"}}' | ~/.claude/hooks/block-anthropic-spend.sh
# Should print nothing (allowed):
echo '{"tool_input":{"command":"git status"}}' | ~/.claude/hooks/block-anthropic-spend.shThen, in a session, ask the agent to run something containing sk-ant- and watch it get blocked. Manage or disable the hook anytime via /hooks.
Extending it
Add a pattern to the PATTERNS array in that one script and every project inherits it — no per-repo setup. Point it at whatever your project’s spend paths are (a batch script name, an internal endpoint, etc.).
The caveat
A Bash hook only sees the command string. If the agent runs python some_script.py and that script calls the API internally, the hook can’t see inside it. So this is a strong tripwire for the obvious cases, not an airtight wall.
For defense in depth, pair it with things the hook can’t do:
- A separate, spend-capped key for the project in the Anthropic Console, so any mistake is bounded — and keep the real key off your dev machine entirely.
- A billing budget alert on the key, so you get emailed the moment unexpected spend starts.
But as a first line that costs nothing and requires no behavior change from the agent, the hook is the piece I reach for first.
Comments