Field notes · 2026-08-25 · RunVouch
My Claude Code cron ran up $1,800 in two nights — the watchdog that stops it at $2
Four documented runaway agent bills ($1,818, $6,000, $437, $150), why per-call metrics hide the loop, and per-run/per-day caps with rv.
I ran autonomous agents and a crypto trading bot unattended for two years. The most expensive bugs never threw an exception. They ran fine, on schedule, and did the same useless thing hundreds of times until an invoice arrived. This post is about the cost version of that failure and the two-line fix I use now.
RunVouch is a dead man's switch, cost cap and outcome check for unattended AI agents: it expects a heartbeat per run, alerts the moment a run or a day crosses a dollar cap, and tells you when a run is missed, stalled, looping or produced no evidence.
What a runaway Claude Code cron bill actually looks like
These are public, documented cases. I am linking each one because the numbers matter more than my opinion.
- anthropics/claude-code #37686 (March 2026). A Max subscriber set up cron jobs running
claude -p --dangerously-skip-permissions. The shell had anANTHROPIC_API_KEYin it, so every run billed the API account instead of the subscription. Opus in agentic loops, roughly 47k input tokens per request, dozens of requests per hour. About $858 on day one, about $960 on day two, $1,818 total before anyone looked. - The $6,000 overnight loop, originally posted on r/ClaudeAI. A loop checked for updates every 30 minutes and re-sent a very large conversation each time. The usage dashboard lagged by days; the first warning was the email after the money was gone.
- openclaw/openclaw #16808 (February 2026). An agent called
process(action:log, sessionId:X)1,535 times in about two hours, each call returning an identical "no new output". About $150, memory from 800 MB to 3 GB, then a crash. The existing watchdog checked that the process existed, not what it was doing. - The $437 LangChain loop (April 2026). A summarizer called
list_files("/data")14,000 times overnight, got the same 1,200 filenames back every time, and only stopped when a token quota cut it off. The author's line: "Every individual call looks like every other normal call. The only signal is cost, and cost is post-hoc."
Four different stacks, one shape: a scheduled or looping process, no per-run ceiling, and monitoring that reports on the wrong unit.
Why per-call metrics hide the loop
Every observability tool I used showed me healthy calls. Latency normal, status 200, tokens per request within range. That is exactly what a loop looks like from the inside. The 14,000th list_files call is indistinguishable from the first.
The loop is only visible at two other granularities:
- Per run. A nightly job that normally costs $0.30 and takes four minutes is now at $40 and 90 minutes. Nothing per-call tells you that; the run total does.
- Per repetition. The same tool with byte-identical input, eight, fifty, a thousand times in one run. No single call is wrong. The sequence is.
The provider's billing page is the wrong place to catch either. In the $6,000 case the dashboard was days behind. In #37686 the runs were billed to an account the user was not watching. You need the cap on the machine that runs the job, enforced while the job is running, not a report afterwards.
The trading bot taught me the same lesson with money instead of tokens. A retry loop on an order endpoint is not "one failed order". It is the same order attempted 400 times with the same payload, and the exchange's per-request logs looked fine.
Cap the run at $2, cap the day, and detect the retry storm
This is what I run now. Register the agent once, with a cadence, a per-run cost cap and an evidence requirement:
rv agent nightly-review --cadence 24h --cap-run-cost 2 --evidence
Then wrap the actual command. RunVouch opens the run, streams the heartbeat, and closes it with the exit code and whatever evidence file you point it at:
rv run nightly-review --evidence-file out.html -- \
claude -p "Review yesterday's PRs and write the summary to out.html" \
--output-format json
For Claude Code specifically there is a plugin that does this through hooks (SessionStart, PostToolUse, Stop). It reads tokens and cost from the transcript, so the cost cap is measured on actual usage, not on an estimate. Every tool call is a heartbeat, which is also how the loop detector gets its data.
What fires, and when:
- BUDGET_RUN: this run passed its cap ($2 above). You get a message within seconds, and your alert webhook can pause the agent so the next scheduled run does not start. In the #37686 case that is one bad run instead of two bad days.
- BUDGET_DAY: the sum across all runs today passed the daily cap. This catches the version where a cron fires far more often than you thought, or where a rescheduling bug launches ten copies.
- RETRY_STORM: the same tool with identical input at least 8 times in one run. Both the OpenClaw and the Magicrails loops would have tripped this within the first minute, not the first hour.
- DRIFT: duration or output size is far outside the last seven runs, measured with median absolute deviation so one previous outlier does not poison the baseline. This is the quiet cousin of the loop, where cost is fine but the job is doing something new.
Alerts go to Telegram, Slack or a webhook. I use Telegram because it is what I actually look at at 07:00.
The part that matters: what happens when the watchdog is down
A cost cap that can also take your job down is a second failure mode, not a fix. rv run fails open: if RunVouch is unreachable, your command still runs exactly as before. You lose the alert, not the job. I would rather miss one notification than have my scheduler depend on someone else's uptime.
The other thing I stopped doing is relying on max_turns or a token quota as the only brake. Those are fine, and you should set them. But a turn limit does not know about dollars, and a quota that stops a job "cold", as in the $437 case, also throws away the work. A per-run cost cap with an alert, plus a pause action on the agent, gives you the choice.
Setup that would have prevented each incident
- #37686:
--cap-run-cost 2plus a daily cap of $10. Day one ends at $10 with four Telegram messages, not at $858 with none. - $6,000 loop: a per-run cap catches the moment context growth makes a single 30-minute cycle cost more than it should. DRIFT catches the growth trend before that.
- OpenClaw #16808: RETRY_STORM at call 8 of 1,535.
- Magicrails $437: RETRY_STORM at call 8 of 14,000.
Free for three agents, $9 Solo, $29 Team, self-host under MIT, EU hosting. Details at runvouch.com.
FAQ
Does the cost cap work with a Max subscription, or only with API keys?
The plugin reads token counts from the transcript, so it works either way. The cap is expressed in dollars using list prices; on a subscription it is a proxy for usage, on an API key it is the actual bill. The #37686 incident is the reason to set it in both cases: you may not be billing where you think you are.
What counts as a retry storm? Will normal loops trigger it?
Same tool, byte-identical input, at least eight times in one run. A loop that reads different files or polls with a changing cursor does not match. Polling the same endpoint with the same arguments does, which is the point.
What if RunVouch itself is unreachable at 03:00?rv run fails open. The wrapped command executes normally; you get no heartbeat and no cap for that run, and a MISSED alert once the service is back if the run was never recorded.
Try it: free for 3 agents · Docs: Claude Code · cron