← Blog

Stop Claude Code before it hits your usage limit

3 August 2026 · A PreToolUse hook that says no

Every usage tracker in this category, mine included, does the same thing: it shows you a number. A number does not stop anything. Your agent cannot see your menu bar, and by the time you see it, the run is already thirty tool calls deep into a weekly cap you needed on Friday.

There is exactly one place in Claude Code where a piece of software you control can say no, and it is a PreToolUse hook. This post shows you how to build one that refuses tool calls when your budget is gone. It works, it is about fifteen lines, and it costs nothing.

Why the usual answer doesn't apply here

Search for "AI budget enforcement" and you get gateways: LiteLLM, Cloudflare AI Gateway, Portkey, and the spend caps built into various platforms. They all work the same way - they sit in front of the API as a proxy, count what passes through, and start rejecting requests when you hit a limit.

None of that is available to you on a Claude Pro or Max subscription. Your subscription authenticates Claude Code directly. The moment you route it through a gateway you are no longer on the subscription - you are on pay-as-you-go API pricing, which for heavy Claude Code use is many times the cost of the plan. You would be paying a large multiple in order to enforce a budget. That is not a trade anybody makes.

So the proxy answer is structurally out. What is left is enforcement on your own machine, in the agent's own control flow. Which is what hooks are.

How PreToolUse actually works

Before Claude Code runs any tool - Bash, Edit, Read, a subagent Task - it fires the PreToolUse event. Every hook registered for it gets the event as JSON on stdin and can write a verdict as JSON on stdout. Roughly:

{"session_id":"...","tool_name":"Bash","tool_input":{"command":"..."},"cwd":"/Users/you/project"}

To block the call, you print this and exit 0:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "why you said no"
  }
}

The reason string is not decoration. It goes back to the model, which reads it and adapts - typically by stopping and telling you, rather than retrying the same call forever. That is the whole mechanism. Print nothing and the call proceeds normally.

A working budget brake in fifteen lines

This one uses ccusage as its source of truth for the active 5-hour block. Install it globally first - if you make the hook run npx on every single tool call you will feel it:

npm i -g ccusage

Then ~/.claude/hooks/budget-brake.sh:

#!/bin/bash
# Deny tool calls once the active 5-hour block passes a dollar ceiling.
CEILING="${CLAUDE_WINDOW_CEILING_USD:-25}"

cat > /dev/null   # drain the event; this brake doesn't care which tool it was

spent=$(ccusage blocks --active --json 2>/dev/null | python3 -c '
import json, sys
try:
    b = json.load(sys.stdin)["blocks"]
    print(b[0]["costUSD"] if b else 0)
except Exception:
    pass
' 2>/dev/null)

# No number? Fail OPEN. A broken meter must never wedge your editor.
[ -z "$spent" ] && exit 0

awk -v s="$spent" -v c="$CEILING" 'BEGIN { exit !(s >= c) }' || exit 0

printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Budget brake: this 5-hour window has used $%.2f of your $%s ceiling. Stop and tell me before doing more work."}}\n' "$spent" "$CEILING"
exit 0

chmod +x ~/.claude/hooks/budget-brake.sh, then register it in ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "/Users/you/.claude/hooks/budget-brake.sh" }
        ]
      }
    ]
  }
}

Test it before you trust it

Do not skip this. A hook that misbehaves sits between you and every tool call you make.

EV='{"session_id":"t","tool_name":"Bash","tool_input":{"command":"echo hi"},"cwd":"/tmp"}'

# Ceiling absurdly high - expect NO output, exit 0
echo "$EV" | CLAUDE_WINDOW_CEILING_USD=1000 ~/.claude/hooks/budget-brake.sh

# Ceiling of $1 - expect a deny verdict
echo "$EV" | CLAUDE_WINDOW_CEILING_USD=1 ~/.claude/hooks/budget-brake.sh

# ccusage unavailable - expect NO output, exit 0 (fail open)
echo "$EV" | PATH=/usr/bin:/bin ~/.claude/hooks/budget-brake.sh

All three of those are the actual commands I ran against the script above before publishing it. The middle one printed a deny; the other two printed nothing.

Three rules I learned the expensive way

1. Fail open, always

If your usage source is missing, slow, stale or returns garbage, the hook must let the call through. The failure mode of a strict hook is that a bad JSON parse silently bricks your editor and you spend forty minutes wondering why Claude Code has stopped doing anything. A budget brake that occasionally lets you overspend is a minor annoyance. One that blocks you at random is worse than no brake at all.

2. Deny only - never return "allow"

The PreToolUse contract also lets you return "permissionDecision": "allow". Do not. An allow verdict bypasses Claude Code's own permission prompt, so a hook that returns allow on the healthy path has quietly auto-approved every destructive command the agent might run. Your budget brake has no business granting permissions. Emit a deny, or emit nothing.

3. Count repeats, or you'll get a loop

Once the brake trips, an agent will sometimes retry the identical call. Each retry re-runs the hook, gets denied again, and burns context. Keep a counter of identical tool-plus-input pairs per session and hard-stop after a few dozen. Same idea catches a genuinely stuck agent hammering one command.

One thing to be clear about the dollars. If you are on a Pro or Max subscription, that costUSD is not money you spent. It is your tokens priced at pay-as-you-go rates - what the usage would have cost without a plan. Your real cost is the monthly plan price. So a "$25 ceiling" is a proxy for "a quarter of my window", not a bill. Set it accordingly, or it will trip far earlier than you expect.

Where the DIY version runs out

I ship this as a product, so take the following with the appropriate salt - but these are the real limits of the script above, and you will hit them:

MeterTab is the version of this with those problems solved: the app keeps a live snapshot so the hook is instant, calibrates the window against your real Claude account limits, exposes the same budget to your agents over MCP so they can downshift instead of dying, and knows when Anthropic is having an incident. It also raises a banner when the brake trips, with a snooze button, because sometimes you do just need to finish the thing.

But if you want the free version, the script above is the free version, and it works. Take it.

MeterTab is a menu-bar meter for Claude Code, Codex, Cursor and six more - free tier, or $19.99 once for Mac, iPhone and Watch.

Download on the App Store

Related