[BUG] Token Usage Statistics Duplicated in stream-json Mode Causing Massive Cost Inflation
## Summary
When using --output-format stream-json, Claude Code SDK incorrectly duplicates token usage statistics for single API responses that contain
multiple content blocks (e.g., thinking + text + tool_use). This leads to severe token usage inflation, potentially causing users to be charged
3-8x more than their actual usage.
## Impact Severity: CRITICAL
- Financial: Users may be overcharged by 300-800% on their Claude API bills
- Scale: Affects all applications using stream-json format (SDKs, automation tools, etc.)
- Real-world case: 660M reported cache tokens likely represent only 80M-220M actual tokens
## Environment
- Claude Code Version: Multiple versions affected (confirmed on v1.0.96)
- Platform: macOS, likely affects all platforms
- Format:
--output-format stream-jsonspecifically - API Model: All Claude models (confirmed on claude-sonnet-4-20250514)
## Detailed Description
### Root Cause
Claude Code SDK splits single assistant messages containing multiple content blocks into separate streaming events, but each event preserves the
complete original usage statistics from the API response.
### Example Evidence
Single API response with ID msg_01Scv8GwYbNqcsotCaz85HSK:
```json
// Original Claude API Response (single call)
{
"id": "msg_01Scv8GwYbNqcsotCaz85HSK",
"content": [
{"type": "thinking", "thinking": "..."},
{"type": "text", "text": "..."},
{"type": "tool_use", "id": "toolu_123", "name": "LS"}
],
"usage": {
"cache_read_input_tokens": 11744,
"input_tokens": 10,
"output_tokens": 377
}
}
// Gets split into 3 separate events in stream-json:
// Event 1: thinking block + full usage stats
// Event 2: text block + full usage stats
// Event 3: tool_use block + full usage stats
Actual Session File Evidence
In real Claude session files, the same cache_read_input_tokens: 11744 appears 8 times for the same message ID:
- Expected: 11,744 cache tokens counted once
- Actual: 11,744 × 8 = 93,952 cache tokens counted
- Inflation ratio: 8x overcount
Reproduction Steps
- Use Claude Code with --output-format stream-json
- Send a request that generates a response with multiple content blocks (thinking + text + tools)
- Examine the session .jsonl file
- Count occurrences of the same message ID with identical usage statistics
- Observe multiple records with same usage data
Expected Behavior
- Single message ID should have usage statistics counted once
- Token usage should reflect actual API consumption
- Stream-json events should split content but preserve usage accuracy
Actual Behavior
- Same message ID appears multiple times with identical usage stats
- Token usage is multiplied by number of content blocks
- Users face massive cost inflation (3-8x typical)
Additional Evidence
Scale of the Problem
- Real user report: 660M cache_read_input_tokens over time period
- With 3-8x duplication factor, actual usage likely 80M-220M tokens
- Potential overcharge: $11,000-$29,000 at current Claude pricing
Related Issues
- May be related to #5034 (duplicate entries) but focuses specifically on financial impact of usage duplication
- Different from session file size issues - this is about billing accuracy
Suggested Fix
- Immediate: Ensure usage statistics are only included in the first event of a split message
- Long-term: Consider keeping message integrity in stream mode or clearly document the splitting behavior
- User protection: Add warnings about potential usage inflation in stream-json documentation
Business Impact
This bug affects Claude Code SDK adoption and user trust:
- Users experience unexpected high bills
- Makes cost prediction impossible
- Impacts enterprise adoption due to budget unpredictability
Request Priority: HIGH
This is a billing accuracy issue that directly impacts user costs. Please prioritize investigation and fix.
---User Impact Statement: This bug has caused significant financial impact for users building production applications with Claude Code SDK.
Immediate attention and fix would be greatly appreciated by the community.
```
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗