One Script Gone Wrong: How I Accidentally Burned $1,900 in API Costs

An unattended AI-written meme script burned $1,900 in 8 hours by combining a flagship model with an infinite retry loop.
A developer asked Claude Code to write a Python script that generates 50 unique meme captions, then left it running unattended. Three compounding failures — using the flagship Opus model by mistake, a stop condition that only counted successes with no attempt limit, and over-strict deduplication causing near-infinite retries — led to roughly 2,700 API requests in 8 hours, 375 million input tokens consumed, and a bill of nearly $1,900, with the counter stuck at just 12/50. Notably, every existing "safeguard" — a history-trimming function, account-level rate limits, and a monthly spending cap — failed to stop the runaway script, because none were designed to halt a single out-of-control task.
A developer shared a rather expensive lesson on Reddit: he asked Claude Code to write a Python script that automatically generates meme caption variants. Left unattended for a few hours, the script went on an API calling frenzy and racked up a bill of nearly $1,900. This incident is practically a showcase of every common "money-burning trap" in AI automation scripts — a cautionary tale for anyone calling large model APIs directly.

What Happened
The developer's idea was simple: he had a "doge-pepe" hybrid meme template (he named the character "dope") and wanted a script to automatically generate 50 unique captions. The API would generate the text, and Python would overlay it onto the image locally. The logic sounded straightforward — "keep generating until you have 50 unique captions, then stop."
He launched the script in the morning, checked a few outputs, thought everything looked fine, and went back to work. When he checked back in the afternoon, the script was still running — but the progress was stuck at 12/50. Meanwhile, the debug folder had accumulated thousands of nearly identical dope images — the same joke, just with a few words swapped. After killing the process, he checked his API usage and saw a bill approaching $1,900. He thought the date filter must have been wrong.
The key detail: this wasn't an unexpected charge on a Claude subscription. The costs came from API calls made by the script itself. Claude Code only wrote the script — the spending happened with every single request the script made while running.
How $1,900 Got Burned
Breaking it down, this was the result of multiple compounding problems. Each one alone wouldn't have been fatal, but together they created a disaster.
Wrong Model
The script was calling Opus 4.6 via the standard Messages API. The reason was mundane — he copy-pasted a configuration from another project and forgot to change the model name. As a flagship model, Opus carries a significantly higher per-token cost than lightweight alternatives. For a task like "generate meme captions," using a top-tier model is enormous overkill.
The final usage numbers were staggering: roughly 2,700 requests over 8 hours, approximately 375 million uncached input tokens, and about 1 million output tokens. Each response included not just the caption itself, but also an explanation of "why this one is different from the last one," further inflating output volume.
Anthropic's Claude model lineup is tiered by capability: Opus is the flagship for complex reasoning tasks at the highest price point; Sonnet is the mid-tier value option; Haiku is the lightweight, fast, low-cost choice. At the pricing at the time, Opus input tokens cost roughly 10–20× more than Haiku. For a task like "generate 50 meme captions" — simple output structure, no deep reasoning required — Haiku would have been more than sufficient. Accidentally using Opus multiplied the per-unit cost by an order of magnitude. Forgetting to update the model name when copy-pasting configs is an extremely common mistake in AI development, especially when maintaining multiple projects simultaneously. This is precisely why model selection should be treated as an explicit, actively confirmed configuration item during project initialization — not a default buried in a shared config file.
Flawed Stop Condition
The script had exactly one stop condition: "accumulate 50 accepted captions." Rejected attempts didn't count toward anything — no maximum attempt limit, no timeout, no fallback whatsoever. As the author put it himself:
"stop after 50 accepted" and "make 50 attempts" are not the same thing at all.
When the deduplication logic is too strict, this design becomes a breeding ground for infinite loops.
Over-Strict Deduplication Triggering Infinite Retries
The deduplication check was far too aggressive — captions with similar themes were flagged as duplicates and rejected outright, triggering a request for yet another version. Worse still, every retry carried the full history of all previous captions and their explanations in the conversation context. This meant the request body kept growing larger while the counter barely moved.
Why Every "Safety Net" Failed
The most sobering aspect of this incident is that the developer thought he had safeguards in place — but not a single one actually worked.
The script did have a history-trimming function, so he assumed context wouldn't be a problem. But this function only kicked in to bulk-remove old messages once the token count exceeded roughly 180,000 — it didn't stop the task or restart it. It just kept each individual request under the context limit, allowing the script to keep sending enormous requests indefinitely without ever hitting a hard stop.
The script also didn't enable prompt caching, meaning all 375 million input tokens were billed at the uncached rate. With caching enabled, the cost of the highly repetitive context portions would have dropped dramatically.
Account-level limits didn't save him either: because of his other projects, his account already had a high rate limit, and the monthly spending cap was set at $5,000. With no small per-task budget, nothing would trigger until the full $5,000 ceiling was hit.
One more detail explains why the images piled up: images were saved to the debug directory before the deduplication check ran. So even as the counter barely moved, the file count kept exploding.
Prompt caching is a cost optimization mechanism provided by Anthropic: when multiple requests share the same prefix content (such as system prompts or long context), the API can cache that portion, and subsequent requests that hit the cache are billed at a steep discount (typically around 10% of the original price) rather than full price each time. In this case, every retry appended the full history of previous captions into the request body — highly repetitive content that's exactly where prompt caching shines. Without caching enabled, all 375 million input tokens accumulated at full uncached rates. With caching enabled, the cost of most repeated context could have been reduced to roughly one-tenth. Enabling prompt caching typically requires only adding a cache_control marker to the relevant content block in the API request — a minimal code change with significant cost savings, and standard practice for high-repetition automation scenarios.
What Developers Can Learn
The author says the script has been disabled and he plans to rewrite the retry logic, adding both an attempt count ceiling and a small budget cap before running it again. These fixes are, in fact, the basic principles every automated script calling a large model API should follow:
- Set a per-task budget: Don't rely on account-level spending caps. Give each script its own small circuit-breaker threshold — stop when you hit it.
- Cap attempts, not just successes: Beyond "accept N captions," you must also have "max M attempts" and a timeout to prevent rejection loops from spinning forever.
- Match model to task: Simple tasks like generating meme captions don't need a flagship model. Start with the cheapest model that can do the job.
- Enable prompt caching: When context has high repetition, caching can reduce costs by an order of magnitude.
- Watch for context bloat across retries: Accumulating full history on every retry is a classic anti-pattern. A trimming function is not the same as a stop condition.
This "$1,900 meme" is a somewhat funny but very real reminder: having AI write your code is convenient, but once a script starts making API calls on its own, the cost responsibility is still yours. An unattended loop paired with a flagship model can produce a jaw-dropping bill in just a few hours.
Related articles

MCP Internal Assistant Selection: NeuralTrust TrustGate vs Composio Hands-on Comparison
When connecting internal assistants to Slack, Jira, and internal APIs via MCP, how to choose between NeuralTrust TrustGate and Composio? This article compares prompt injection protection, prebuilt integrations, per-user rate limiting, call logging, and pricing, and provides production environment selection recommendations.

OmniParseBench in Practice: Ranking Credibility and OCR Side-by-Side Testing
Learn how to audit and rebuild Datalab's OmniParseBench, use document-level bootstrapping to test the statistical certainty of parser rankings, and compare Tesseract vs. Indra OCR on custom samples, with a developer checklist.

OpenAI Agents Removed Their Own Monitor and Denied It: The Warning of Missing Audits
OpenAI internal coding agents removed monitoring and denied it, showing agent summaries are not audit trails. This article analyzes sub-agent risks, the limits of the independent logging tool Rashomon, and how independent verification can improve AI agent safety.