Skip to content

Claude Code parser double-counts tokens when assistant message is split across multiple JSONL lines #1

Description

@ttthree

Summary

The Claude Code parser currently assumes each assistant JSONL line represents one distinct billable model response. In practice, Claude sessions can contain multiple assistant lines with the same message.id and identical message.usage, while splitting the response into separate content chunks (text, tool_use, etc.).

This causes tokmon to over-count Claude Code tokens and cost.

Evidence

Recent example:

  • Session: d7a03194-a9d6-4ed8-afb1-a20c5ff8fc82
  • Claude Code version: 2.1.114
  • Same assistant.message.id appears across multiple lines with duplicated usage

For this session:

  • Current parser totals: input=76670, output=1566, cacheRead=178398
  • Deduped by assistant.message.id: input=36519, output=1085, cacheRead=124188

This is not limited to the latest Claude Code version. A scan of older sessions found the same pattern in prior versions too, including 2.1.69 and 2.1.101.

Examples found during investigation:

  • da699e25-09cc-46ab-b113-6af79297460e.jsonl (2.1.69): 238 assistant lines, 44 duplicate message-id lines
  • eac7bd69-cb14-43f8-8726-a4fc022bc619.jsonl (2.1.69): 147 assistant lines, 17 duplicate message-id lines
  • d7a03194-a9d6-4ed8-afb1-a20c5ff8fc82.jsonl (2.1.114): 8 assistant lines, 3 duplicate message-id lines

Current assumption in code

In src/parsers/claude-code.ts, parseClaudeSessionFile() currently:

  • iterates every JSONL line
  • treats every type === "assistant" record as a separate token-bearing event
  • adds message.usage directly into session/model totals

That assumption is not format-safe.

Expected behavior

For Claude Code session parsing:

  • token accounting should be deduplicated by logical assistant response, likely keyed by assistant.message.id
  • tool usage extraction should still include all content blocks, even if multiple lines belong to the same assistant response
  • if a line is malformed or missing expected fields, parser should emit a warning rather than silently assuming the structure is valid

Proposed fix

  1. Update the Claude Code parser to track seen assistant message IDs per session.
  2. Only count message.usage once per unique assistant response.
  3. Continue accumulating tool_use blocks from all assistant lines.
  4. Add runtime validation / warnings around parser format assumptions so future Claude Code format changes are easier to detect.

Why this matters

This bug inflates Claude Code token and cost reporting, which directly undermines the dashboard's core purpose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions