anthropics / anthropics/claude-code

[BUG] Usage "Detailed Breakdown" over-counts cache read/write/input ~2.5× — sums usage per content-block line, not per API request

Aperta
#94,224 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:cost bug has repro
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

### Environment
- Claude Code desktop app (Code tab), v2.1.270
- Surface: the Usage pill under the composer → **Detailed Breakdown**

### Summary
The per-session **Detailed Breakdown** (Input / Output / Cache read / Cache write) counts token usage **once per transcript content-block entry** instead of **once per API request**.

A normal assistant turn is written to the session `.jsonl` as several lines — one per content block (`thinking`, `text`, `tool_use`) — and **every one of those lines carries the identical `usage` object** (same `requestId`, byte-for-byte the same token counts). The Breakdown sums per line, so each request's usage is multiplied by its number of content blocks (~2.5 on average, 3 for a typical tool-calling turn).

Net effect: **Cache read — the dominant figure — is inflated ~2.5×**, which makes it misleading.

### Impact
Cache read dominates token volume, so a 2.5× overstatement makes the headline number actively confusing (a session that truly read ~168M cache tokens is shown as ~418M). If the session **Cost** estimate is derived from the same per-line sum, it's overstated too. The 5-hour / weekly **limit bars** appear unaffected (presumably computed server-side from real usage) — so the Breakdown disagrees with the bars, compounding the confusion.

### Evidence (one real session)
The Breakdown as displayed, vs. the two ways of summing the transcript's `usage` metadata:

| Field | Breakdown UI | Summed per **API request** (`requestId`) | Summed per **transcript line** |
|-------------|-------------:|-----------------------------------------:|-------------------------------:|
| Input | 1.6k | 629 | **1,571** |
| Cache read | 418M | 168,204,894 | **418,001,293** |
| Cache write | 5.5M | 2,004,667 | **5,539,964** |

The UI matches the **per-line** sum on all three fields to displayed precision.

Session shape: **315 unique API requests, 787 assistant lines carrying `usage`.** 184 requests appear 3× in the transcript (thinking+text+tool_use), 53 appear 2×, 61 once → weighted average ≈ 2.5, matching the 2.49× inflation (168M → 418M). For **every** repeated request (254/254), the `usage` fields are **byte-identical** across its lines — the same request's usage is being re-counted, not distinct reads.

### Root cause hypothesis
The Breakdown aggregates `message.usage` per transcript entry. It should **dedupe by `requestId` (or `message.id`) and count each request's `usage` once.** `cache_read_input_tokens`, `cache_creation_input_tokens`, and `input_tokens` describe a single API request; duplicating them per content block multi-counts them.

### Minimal repro
Run against any session transcript (`~/.claude/projects//.jsonl`):

```python
import json, sys
from collections import defaultdict
by = defaultdict(list)
for line in open(sys.argv[1]):
try: o = json.loads(line)
except: continue
if o.get('type') != 'assistant': continue
u = (o.get('message') or {}).get('usage')
if not u: continue
rid = o.get('requestId') or o['message'].get('id')
by[rid].append(u.get('cache_read_input_tokens', 0))
naive = sum(v for xs in by.values() for v in xs)
actual = sum(xs[0] for xs in by.values()) # identical across a request's lines
print(f"per-line (what the UI shows): {naive:,}")
print(f"per-request (correct): {actual:,}")
print(f"inflation: {naive/actual:.2f}x")
```

**Expected:** the Breakdown shows the per-request total (~168M here). **Actual:** it shows the per-line total (~418M).

### Notes
- **Output** does *not* follow the per-line rule — the UI showed 4.3k, well below even the true per-request output (~475K) — so it has separate handling and is likely a distinct bug.
- **Subagents** are tracked separately in the UI ("What's using your limits"), and run their own transcripts, so the figures above are main-thread only.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start with a session .jsonl transcript and the supplied Python repro, then trace the aggregation behind the Usage pill’s Detailed Breakdown. Verify how requestId or message.id is handled and compare the displayed totals with per-request sums. Done means cache read, cache write, and input usage are counted once per API request and the Breakdown matches the expected total.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
cli, developer-experience
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
58/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.