DEV Community

강해수
강해수

Posted on • Originally published at riversealab.com

My Anthropic bill dropped from $312 to $156 after I added two bash hooks to Claude Code

60% of a $312 Anthropic bill came from a single pattern: Claude Code hitting a D1 migration failure, then spinning up 7–8 retry Bash calls trying to diagnose what went wrong. Each loop burned 40–60K tokens. Three or four loops per session, and you're looking at $0.50–$0.70 just evaporating.

The fix wasn't prompt engineering. It was a PostToolUse hook that fires the moment wrangler d1 migrations apply exits non-zero — before the agent has a chance to start its retry spiral.

#!/bin/bash
# post_bash_hook.sh
COMMAND="$1"
EXIT_CODE="$2"

if echo "$COMMAND" | grep -q "wrangler d1 migrations apply"; then
  if [ "$EXIT_CODE" != "0" ]; then
    echo "ALERT: D1 migration failed (exit $EXIT_CODE). Check schema state." >&2
    curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/storage/kv/namespaces/$KV_NS/values/migration_failed" \
      -H "Authorization: Bearer $CF_API_TOKEN" \
      -d "1" > /dev/null
  fi
fi

exit 0
Enter fullscreen mode Exit fullscreen mode

A Slack bot polls that KV key every 3 minutes. When it flips to 1, I get pinged and can intervene before Claude Code decides to investigate further on my token budget. Six months running this setup: zero schema-mismatch incidents, and the next month's bill came in at $156.

The other half of the chain is a PreToolUse hook that blocks wrangler deploy whenever the agent is on main — learned that one the hard way after a production deploy went out from the wrong branch and left two Workers in a broken state for five minutes. The thing most people miss: when your hook returns exit 2, Claude Code reads whatever you wrote to stderr as context. A vague BLOCK does nothing useful. BLOCK: wrangler deploy on main — use staging namespace instead actually redirects the agent correctly.

There's also a pre-commit hook at the end of the chain that scans staged diffs for hardcoded production binding names and secret key patterns — a last filter before anything reaches git history.

I wrote up the full breakdown — including the exact .claude/settings.json structure, how hook matcher patterns work (and where they don't), and the FAQ on execution order guarantees — over on riversealab.com.

Full post →

Top comments (0)