I set a $1 AWS budget before letting Claude Code loose on a hobby project. Less than 24 hours later I had a $106.26 charge.
My mistake was treating the budget as a limit. It is an alert, and a slow one. This post covers what actually happened, why an agent can burn money this fast, and the layered setup I use now. I'll also be clear about what I have not verified, because I only have one data point: my own bill.
What happened
The setup was ordinary. Claude Code pointed at Amazon Bedrock, a hobby project, and a $1 monthly cost budget with an email alert. I assumed $1 meant I would be stopped at roughly $1.
I wasn't. By the time anything told me I was over, I was over by two orders of magnitude.
Two separate things went wrong, and they compound.
1. The budget could not have stopped me
AWS Budgets does not sit in the request path. It reads billing data and compares it to your threshold. The AWS documentation is direct about the cadence: budget information is updated up to three times a day, typically 8 to 12 hours after the previous update. It also warns that you might incur costs that exceed your threshold before Budgets can notify you.
So a budget is a smoke detector, not a circuit breaker. If something spends $100 in the gap between two updates, the first signal arrives after the money is gone.
2. The agent was spending much faster than I expected
I did not hit an infinite loop, as far as I can tell. What I saw was the agent re-verifying its own work again and again. That alone is a lot of requests. The part I underestimated is what each request costs.
Claude Code sends the whole conversation with every request, and every tool use produces another request carrying the accumulated tool results. The cost docs describe the same thing: a long session draws on the full history on each turn, even for a one-line follow-up.
Many requests multiplied by a large context each time is a bill that grows quickly while the agent looks perfectly productive.
Figure 1: Agent spend accrues continuously, while budget data arrives in coarse, delayed updates.
A rough model of the cost
I don't have a per-request breakdown of my bill, so this is an illustration, not a measurement.
Suppose an agent session has a 100k token context and makes 100 requests while it checks its own work. That is 10 million input tokens. At a hypothetical $3 per million input tokens, that is $30 if every request pays full price to re-read the context.
With prompt caching working, those repeated reads are billed at the cached rate, which is a fraction of base input price on Anthropic's published pricing (check the current Bedrock rates for your model). The same 10 million tokens could cost a fraction of that $30.
Two details make this worse in practice:
- Cache lifetime depends on how you connect. The Claude Code docs say the cache lasts an hour on a subscription, and five minutes by default on an API key or cloud provider. A pause longer than the lifetime means the next request reprocesses the full context.
-
Everything that sends a request carries context. Scheduled tasks, subagents, and compaction each make their own requests.
/compactitself reads the whole conversation it summarizes, so compacting a huge context is a large request.
Figure 2: Unguarded, the only brake is a delayed email. Guarded, a budget action and cheaper requests bound the damage.
What I changed
Three changes, in order of how much I trust them.
A budget action that revokes access
AWS Budgets can do more than send email. A budget action can run automatically when a threshold is crossed, and the available actions include applying an IAM policy or an SCP, or targeting specific EC2 or RDS instances.
I use an IAM action that attaches an explicit deny on Bedrock invocation to the role Claude Code uses.
The deny policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyBedrockInvokeOnBudgetBreach",
"Effect": "Deny",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:Converse",
"bedrock:ConverseStream"
],
"Resource": "*"
}
]
}
Create it as a customer managed policy, but do not attach it. The budget action attaches it when the threshold is hit.
The action itself, via the CLI. Treat this as a sketch and check each flag against the current aws budgets create-budget-action reference before running it:
aws budgets create-budget-action \
--account-id 123456789012 \
--budget-name agent-hobby-budget \
--notification-type ACTUAL \
--action-type APPLY_IAM_POLICY \
--action-threshold ActionThresholdValue=80,ActionThresholdType=PERCENTAGE \
--definition 'IamActionDefinition={PolicyArn=arn:aws:iam::123456789012:policy/DenyBedrockInvoke,Roles=[claude-code-role]}' \
--execution-role-arn arn:aws:iam::123456789012:role/BudgetsActionRole \
--approval-model AUTOMATIC \
--subscribers SubscriptionType=EMAIL,Address=you@example.com
Two things to get right:
- The execution role. AWS Budgets needs a service role it can assume, with permission to apply IAM policies. AWS publishes an example policy for this ("Allow AWS Budgets to apply IAM policies and SCPs").
- Who the deny attaches to. The action only affects the users, groups, or roles you list. If Claude Code runs with different credentials than the ones you listed, the deny does nothing.
An honest limitation: this still runs off the same delayed billing data. It turns an email into an actual cutoff, but it does not remove the lag. That is why the threshold needs to sit well below what you can afford to lose, not at it. My $1 budget was the right idea with no action attached.
I also had to be careful about which costs the budget watched. Model usage on Bedrock may appear under service names that are specific to the model, so a budget filtered to one service name can miss spend. For a hobby account, an account-wide cost budget is simpler. Check Cost Explorer to see how your own usage is grouped before you rely on a filter.
Prompt caching, verified rather than assumed
Prompt caching is on by default in Claude Code, but "on" is not the same as "working". Claude Code shows a prompt cache line in the session block of /usage: request count, share of input tokens served from cache, misses, and whether the cache is currently warm.
That line is the thing to read. A low cache share on a long session means you are paying full price to re-read your codebase every turn, which is exactly the failure mode that hurt me.
Compact and clear on purpose
- Run
/compactbefore a session gets large, and give it instructions about what to keep. - Use
/clearwhen you switch to unrelated work. Stale context is paid for on every later request. - Check
/usageduring long runs instead of after.
None of these is a hard control. They lower the rate. The budget action is what bounds the worst case.
Figure 3: Each layer and its guarantee. Only the IAM deny or a request-path gateway actually stops requests.
Controls compared
| Control | Stops requests? | Latency | Notes |
|---|---|---|---|
| Budget email alert | No | Hours | Tells you after the fact |
| Budget action (IAM deny) | Yes | Hours, tied to billing updates | Bounded worst case, not a tight cap |
| Prompt caching | No | None | Lowers the rate; verify it with /usage
|
/compact, /clear, /usage
|
No | None | Habits, and easy to forget |
| LLM gateway with per-key limits | Yes | Near real time | Sits in the request path; extra infrastructure |
On cloud providers, the Claude Code docs say spend controls live in your cloud billing console. For per-user attribution they point to OpenTelemetry export or an LLM gateway. If you run agents for a team, or you want a cap that reacts in seconds instead of hours, a gateway is the control that matches the problem. For a single hobby account, a budget action is a reasonable first step.
Checklist
- [ ] A budget exists, and it has an action, not only an email.
- [ ] The action's role list matches the credentials Claude Code actually uses.
- [ ] The threshold is set well below what you can afford to lose, because billing data lags.
- [ ]
/usageshows a healthy prompt cache share on a long session. - [ ] Long sessions get
/compact; unrelated tasks get/clear. - [ ] You know where your own spend appears in Cost Explorer.
Discussion
I treated the budget as a limit and it cost me. If you run agents against a cloud provider, how are you capping spend: budget actions, a gateway, or watching /usage? I'm especially interested in anyone running a gateway for a single developer, and whether the setup was worth it.
Original post and discussion on LinkedIn.



Top comments (0)