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

Resolved 💬 5 comments Opened Feb 21, 2026 by monk1337 Closed Mar 24, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] 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:

```text
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

{
    "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.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗