[Bug] Excessive cache writes causing unexpectedly high token usage and costs

Status Fixed / completed
Reported on v2.1.185
Maintainer reply None cached
Activity 7 comments · opened Jun 22, 2026 · closed Jul 22, 2026

Unexpected excessive cache reads/writes causing $94.46 charge during simple repository inspection

Summary

While using Claude Code for a relatively simple task involving gathering information from a local repository, the session unexpectedly generated millions of cache reads and cache writes, resulting in a $94.46 charge.

This behavior seems abnormal and possibly related to repeated failed requests or retry behavior. I have been using Claude Code for a long time and have not seen this type of cache usage or cost pattern before.

I would like help investigating whether this was caused by a bug.

Environment

  • Platform: macOS / darwin
  • Terminal: VS Code
  • Claude Code version: 2.1.185
  • Feedback ID: 476d44e0-a293-4d4d-9eeb-9c396c4d3426

What happened

Claude Code was asked to gather information from the repository. During this process, usage spiked unexpectedly, with extremely high cache read and cache write counts. The resulting cost was $94.46, which seems far beyond what should be expected for this type of task.

The logs also show repeated 400 invalid_request_error failures related to reddit.com being inaccessible to Anthropic’s user agent. It looks like these failures may have been retried multiple times.

Error logs

[
  {
    "error": "Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"The following domains are not accessible to our user agent: ['reddit.com']. Read more: https://support.anthropic.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler\"},\"request_id\":\"req_011CcJARPCUUZTDz4TQTSvPi\"}\n    at generate (/$bunfs/root/src/entrypoints/cli.js:12:68924)\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:52:7688)\n    at processTicksAndRejections (native:7:39)",
    "timestamp": "2026-06-22T10:45:40.842Z"
  },
  {
    "error": "Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"The following domains are not accessible to our user agent: ['reddit.com']. Read more: https://support.anthropic.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler\"},\"request_id\":\"req_011CcJARPqBLQyZKiiWJrfA1\"}\n    at generate (/$bunfs/root/src/entrypoints/cli.js:12:68924)\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:52:7688)\n    at processTicksAndRejections (native:7:39)",
    "timestamp": "2026-06-22T10:45:40.980Z"
  }
]

The full log contains many repeated errors of the same form.

Expected behavior

A simple repository information-gathering task should not generate millions of cache reads/writes or result in a $94.46 charge.

If an external domain is inaccessible, Claude Code should fail gracefully, avoid repeated expensive retries, and avoid creating excessive cache traffic.

Actual behavior

The task appears to have triggered repeated failed requests and extremely high cache activity, leading to an unexpectedly large charge.

Requested help

Could you please investigate:

  1. Whether this was caused by a Claude Code bug, retry loop, or cache accounting issue.
  2. Why repeated reddit.com access failures led to such high cache reads/writes.
  3. Whether safeguards can be added to prevent this from happening again.
  4. Whether a refund or credit can be issued for the unexpected $94.46 charge.

Thank you.

View original on GitHub ↗

7 Comments

cnighswonger · 2 months ago

A few things worth separating here before concluding the $94 is a bug per se — the JSONL can tell you exactly where the cost came from, and the answer often shifts the framing.

1. The 400 invalid_request_error for reddit.com is a server-side WebFetch refusal, not evidence by itself of a low-level HTTP retry loop. Those req_011CcJAR... IDs are Anthropic-server-generated — each failed WebFetch already reached the server, which decided not to crawl, returned the 400, and (per Anthropic's documented pricing) that request itself is generally non-billable. The interesting question is what the **assistant did after the 400 came back** — typically the agent reads the error, decides what to try next, and that next turn re-sends the full conversation context (which by now includes the failed-attempt history). The agent may still be making repeated conversational attempts after seeing the 400s; the cost driver is the context retransmits, not the failed network calls themselves.

2. Raw cache_read / cache_write totals are not directly comparable to cost. In Claude Code, cache_read_input_tokens is the bulk of every turn (the whole conversation is re-sent each response), and it bills at roughly a tenth of input tokens. cache_creation_input_tokens bills at more than input tokens. So "millions of cache reads" can be a much smaller cost than the headline number suggests, and "many cache writes" is where the actual money is. The breakdown you want is:

F=$(ls -t ~/.claude/projects/*/*.jsonl | head -1)   # the session in question

python3 - "$F" <<'PY'
import json,sys
k=dict(input=0,cache_create=0,cache_read=0,output=0)
n_assistant=0
for line in open(sys.argv[1]):
    try: o=json.loads(line)
    except: continue
    if o.get("type")!="assistant": continue
    n_assistant+=1
    u=(o.get("message") or {}).get("usage") or {}
    k['input']+=u.get('input_tokens',0)
    k['cache_create']+=u.get('cache_creation_input_tokens',0)
    k['cache_read']+=u.get('cache_read_input_tokens',0)
    k['output']+=u.get('output_tokens',0)
tot=sum(k.values()) or 1
print(f"assistant turns: {n_assistant}")
for n,v in k.items():
    print(f"{n:13}{v:>15,}  {v/tot*100:5.1f}%")
print(f"{'TOTAL':13}{tot:>15,}")
PY

If cache_create is dominating the share, that's where the bill came from — and the question becomes "what kept invalidating the cache prefix?" One possible cause is a tool result whose content changes byte-for-byte each turn (a timestamp or a counter that gets re-rendered on every retry). If cache_read dominates but the cost is still high, the absolute size of cache_read × the number of turns is the explanation, not double-billing.

3. If you want to map it to the dollar figure, take the breakdown above and multiply each row by the current Anthropic per-MTok rate for the model shown in your JSONL / usage report for that token type. If the multiplied total comes out to ~$94, the accounting is right and the question is "what should have stopped this earlier" (which is a real product question — an automatic guardrail on cumulative-turns-without-progress would help). If the multiplied total comes out materially under $94, that's the bug and it's worth attaching to your refund request with the JSONL as evidence.

The Anthropic doc on what isn't billed says failed requests aren't charged. That doesn't cover the conversational-context retransmits that follow a failed tool call, which is the actual mechanism here as far as I can tell from the outside — but that distinction is exactly the thing the JSONL can confirm or refute.

— AI Team Lead

roygal-pentera · 2 months ago

@cnighswonger Thanks, I ran the JSONL breakdown and checked it against the model that was used here: Opus 4.6.

Using Anthropic’s current listed Opus 4.6 rates:

  • input: $5 / MTok
  • cache writes: $6.25 / MTok for 5m cache, or $10 / MTok for 1h cache
  • cache reads: $0.50 / MTok
  • output: $25 / MTok

The session totals I see are:

assistant turns: 49
input                  1,149
cache_create         253,486
cache_read         2,589,312
output                39,629
TOTAL              2,883,576

That prices out to approximately:

input:        1,149     × $5/M       = ~$0.01
cache_create: 253,486   × $6.25-10/M = ~$1.58-$2.53
cache_read:   2,589,312 × $0.50/M    = ~$1.29
output:       39,629    × $25/M      = ~$0.99

total: ~$3.88-$4.82

So I agree with the framing that the failed WebFetch calls themselves probably are not the direct charge driver. But the important point is that the follow-up conversational turns should already be reflected in these token totals. If this JSONL is the complete session being attributed to the ~$94 charge, I don’t see a path from the recorded token usage to that dollar amount.

That seems to leave a few possibilities:

  • the $94 includes usage outside this JSONL/session
  • there are separate server-side tool or platform charges not represented in these assistant usage rows
  • the billing UI is aggregating/attributing multiple runs together
  • there is a billing/accounting bug

Given this JSONL alone, the token accounting looks closer to $4-$5, not $94.

cnighswonger · 2 months ago

@roygal-pentera Thanks for running the numbers cleanly. The 20x gap between the token math ($4–5) and the billed amount ($94) is real and the four candidates you listed are the right shape.

One hypothesis worth testing before any of them: the local JSONL captures usage rows from successful responses only. A 400 invalid_request_error for a refused WebFetch domain returns an error envelope (type, error, request_id), not a successful message envelope with a usage block. The failed turn never appears in the JSONL's token accounting. The request body still traveled to Anthropic's serving tier and was processed at least far enough to make the refusal decision. Whether that work is billable as input-token traffic on metered API plans is the open question — I haven't seen public docs that confirm it either way, so this is the hypothesis to probe, not a fact.

A clarifying question, since the framing changes a lot depending on the answer: was this session billed against a Max subscription or a metered API key / Console billing? The dollar math you ran assumes per-token metered pricing, which only applies on the API plan. Max subscriptions don't bill by token at all — they bill by usage-window quota with unified-* rate-limit headers. If this was Max-quota, the $94 figure would have come from somewhere other than per-token pricing entirely.

If it was metered API, the JSONL-misses-failed-turns hypothesis becomes testable cheaply: compare the assistant-turn count in the JSONL against the request-id count in Anthropic's Console usage log for the same window. Any gap is traffic that may be absent from local JSONL accounting and would be worth flagging in your Anthropic support ticket as the candidate source of the difference.

— AI Team Lead

roygal-pentera · 2 months ago

@cnighswonger I rechecked the local logs and found the issue with my earlier math: I had only counted the top-level transcript. The session also produced a large subagent tree, and those subagent JSONLs carry their own usage.

Aggregating the successful recorded usage across the full session tree gets much closer to the billed amount. The top-level chat itself was small, but one research branch recursively spawned a large number of subagents doing web research. That branch alone accounts for most of the recorded cost.

The subagent tree reached 5 levels deep below the main session:

depth 1: 4 agents
depth 2: 3 agents
depth 3: 9 agents
depth 4: 33 agents
depth 5: 56 agents

Example chain:

main
→ Research OpenAI/Anthropic abort
→ Research OpenAI abort patterns
→ Research OpenAI abort/stop patterns
→ Developer discussions abort patterns
→ Search blog posts and tutorials

So the updated framing is:

  • the top-level JSONL alone was misleading
  • the full session tree explains most of the charge
  • the dominant cost driver was uncontrolled recursive subagent fanout during “thorough” web research, not simply the failed Reddit fetches
  • there may still be some remaining delta from failed/unrecorded requests or billing-side aggregation

From my side, the product concern is broader than billing: Claude Code should expose and/or guard recursive subagent fanout. A single high-level research request should not silently expand into a deep tree of subagents without a visible warning, budget/cost guardrail, or explicit user confirmation.

It would also be useful to make the maximum subagent recursion depth configurable, so teams can cap fanout based on their workflow and risk tolerance.

cnighswonger · 2 months ago

@roygal-pentera Nice diagnosis — 105 agents across 5 levels is the kind of thing the top-level transcript doesn't make obvious locally, and the math closing the gap from the aggregated subagent tree is the cleanest confirmation you can ask for.

The product asks at the end of your update are exactly the shape #68619 ("Subagent spawning and subagent pattern bugs trigger infinite recursion, infinite token usage") is asking for. That thread is carrying more reactions than this one and is already discussing hard recursion-depth limits and global live-agent caps. Your concrete case — a depth-5 web-research tree from a single high-level prompt, with the cost only visible after stitching subagent JSONLs together — would be a strong datapoint to add over there. Two angles that feel additive rather than redundant:

  • A pre-execution surface that shows the agent fanout shape before it commits (depth-N plan visible, optional confirm). The /usage slash command does roll subagent consumption up across the local session tree post-hoc, but it doesn't help when the fanout has already happened — by then the depth-5 web-research tree is already done.
  • Per-project configurable caps on top of the global defaults, so teams can tune depth and concurrent fanout to their workflow.

The structural gap your case shows is that the top-level transcript can look cheap while the full subagent tree is doing most of the billed work. /usage rolls it up after the fact; the missing piece is bounding and previewing the tree before it expands.

— AI Team Lead

wozcode-helper · 2 months ago

that spike to millions of cache reads/writes and the $94.46 charge looks like the reddit 400 errors triggered repeated retries that blew up cache usage. ngl, wozcode cut my token spend ~50% w better caching — https://wozcode.com

roygal-pentera · 1 month ago

Claude Code v2.1.217 introduced 2 new environment variables that allow you to configure the concurrency and spawn depth of subagents.

CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS and CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH.

https://code.claude.com/docs/en/changelog#:~:text=Added%20a%20cap,allow%20deeper%20nesting