Skip to content

[BUG] JSONL session transcripts missing final message_stop event, output token counts unreliable #27361

Description

@monk1337

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Session JSONL files (~/.claude/projects/<project>/<session-id>.jsonl) log every content block during streaming but never log
the final message_stop event.

  • output_tokens in the usage field is a mid-stream snapshot, not the final count
  • stop_reason is always null, no line ever records "end_turn"
  • Output tokens are undercounted by ~2x compared to actual content
  • Input/cache token counts are accurate (set at request start)

Evidence

Single API call producing thinking + text + 3 tool_use blocks:

Line 41: thinking    output_tokens=8     stop_reason=null
Line 42: text        output_tokens=8     stop_reason=null
Line 43: tool_use    output_tokens=8     stop_reason=null
Line 45: tool_use    output_tokens=8     stop_reason=null
Line 46: tool_use    output_tokens=310   stop_reason=null  ← partial update, still not final

Full session comparison (128 API calls, Opus 4.6, 49 min):

┌───────────────────────────────────────────┬───────────────┐
│                  Method                   │ Output Tokens │
├───────────────────────────────────────────┼───────────────┤
│ JSONL output_tokens summed                │ 23,725        │
├───────────────────────────────────────────┼───────────────┤
│ tiktoken cl100k_base on extracted content │ 45,050        │
├───────────────────────────────────────────┼───────────────┤
│ Undercount                                │ ~1.9x         │
└───────────────────────────────────────────┴───────────────┘

Impact

- Cost tracking tools (ccusage, cccost, custom scripts) that parse JSONL get wrong output counts
- No way to reconcile per-session cost against Anthropic billing dashboard
- Users building usage reports or budgeting around JSONL data are underestimating spend

### What Should Happen?

Each API call should have a final JSONL line with:
- `stop_reason: "end_turn"` (or `"tool_use"`)
- Accurate final `output_tokens` count matching what Anthropic actually bills

This is one additional JSONL line per API call , the final streaming event that currently gets discarded.

### Error Messages/Logs

```shell
{
  "type": "assistant",
  "requestId": "req_011CYLnnca3yt6Axy4nsUrRM",
  "message": {
    "role": "assistant",
    "stop_reason": null,
    "usage": {
      "input_tokens": 1,
      "cache_creation_input_tokens": 510,
      "cache_read_input_tokens": 40716,
      "output_tokens": 310
    }
  }
}

No line in the entire 1043-line JSONL transcript has stop_reason set to anything other than null.

Steps to Reproduce

  1. Run any Claude Code session with multiple tool calls
  2. After session ends, open the JSONL transcript at ~/.claude/projects/<project>/<session-id>.jsonl
  3. Search for stop_reason , every line shows null
  4. Sum output_tokens from the last JSONL line per unique requestId
  5. Compare against actual output content (extract all thinking/text/tool_use blocks, tokenize with tiktoken)
  6. Observe ~2x undercount

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

No response

Claude Code Version

2.1.50

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Session analyzed: 128 API calls, 49 minutes, ~11.3M total input tokens (mostly cached), heavy MCP tool usage. The undercount is consistent across the entire session, not isolated to specific request types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleIssue is inactive

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions