[BUG] Transcript rows zero every usage counter but keep usage.cache_creation — cache_creation_input_tokens != sum(cache_creation)
What's wrong
Some assistant rows in session transcripts (~/.claude/projects/**/*.jsonl) have every top-level usage counter set to 0, while usage.cache_creation still holds the real write. That breaks the invariant documented on the prompt-caching page, under 1-hour cache duration: "the current cache_creation_input_tokens field equals the sum of the values in the cache_creation object."
In most cases another row of the same message (same message.id + requestId) carries the correct, consistent usage. So this looks like a row written with a zeroed usage snapshot, not an API response that disagrees with itself.
Example
Two rows of one message, CC 2.1.237, claude-opus-5, both with content: [text]. Usage fields only:
{"input_tokens": 2, "cache_creation_input_tokens": 977, "cache_read_input_tokens": 316685, "output_tokens": 5175, "cache_creation": {"ephemeral_1h_input_tokens": 977, "ephemeral_5m_input_tokens": 0}}
{"input_tokens": 0, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 0, "cache_creation": {"ephemeral_1h_input_tokens": 977, "ephemeral_5m_input_tokens": 0}}
How often (one machine, 2026-07-10 → 2026-09-19)
- 540,177 assistant rows carrying
usage.cache_creation, across 153,985 distinct messages - 153 messages have at least one row where
cache_creation_input_tokens != sum(cache_creation.*) - in 136 of them, another row of the same message is consistent
- 17 messages have no consistent row at all. For those, the transcript records a cache write only in the nested object.
- Direction: almost always
cache_creation_input_tokens = 0with a non-zero split. The largest single row is 417,471 tokens reported as 0. - Seen on 20 CC versions from 2.1.205 through 2.1.275, and on
claude-opus-5,claude-opus-4-8,claude-fable-5,claude-fable-5-1. - Stop reasons
end_turnandtool_use. Content typestext,thinking,tool_use. None are API-error rows.
Why it matters
Anything that accounts for cache writes from transcripts reads the top-level field and silently undercounts. That includes cost reports and cache-rebuild analysis. Readers that de-duplicate by message id and keep the first row are affected whenever the zeroed row comes first.
Expected
Every row written for a message carries the same usage, or at least one that satisfies the documented invariant.
Repro (read-only)
For each assistant row with message.usage.cache_creation, compare cache_creation_input_tokens with the sum of the cache_creation values. Then group by (message.id, requestId).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗