Skip to content

Token widgets show In/Out/Total: 0 when session transcript exceeds ~512MB #550

Description

@LZong-tw

Summary

Built-in tokens-input, tokens-output, and tokens-total widgets permanently show 0 for long-lived sessions whose transcript JSONL is larger than Node's maximum string length (~512MB / 0x1fffffe8 characters), while other metrics (session cost, custom streaming parsers) continue to work.

Environment

  • ccstatusline: 2.2.27 (latest on npm)
  • Claude Code: 2.1.226
  • OS: Windows 11
  • Node: via nvm4w global install

Reproduction

  1. Open a Claude Code session whose transcript_path points at a very large JSONL (observed: ~844 MB under ~/.claude/projects/.../<session>.jsonl).
  2. Status line configured with tokens-input, tokens-output, tokens-total, and session-cost.
  3. Observe:
    • In: 0 Out: 0 Total: 0
    • Cost: $... still correct (comes from statusline stdin payload, not transcript scan)
    • Custom commands that stream/chunk-read the same transcript still report real cache totals

Minimal reproduction with the installed binary:

# payload with transcript_path set to an ~844MB session jsonl
echo '{"transcript_path":".../huge.jsonl","cost":{"total_cost_usd":1537.20},"model":{"display_name":"Sonnet 5"},"version":"2.1.226"}' | ccstatusline
# → In: 0 Out: 0 Total: 0  (Cost still shown)

Direct Node repro of the underlying failure:

fs.readFileSync(hugeTranscriptPath, 'utf8')
// Error: Cannot create a string longer than 0x1fffffe8 characters

Root cause

getTokenMetrics() (and readJsonlLines) loads the entire transcript with fs.readFile(..., "utf-8"):

async function readJsonlLines(filePath: string) {
  const content = await readFile(filePath, "utf-8");
  return splitJsonlContent(content);
}

When the file exceeds Node's max string size, that throws. getTokenMetrics swallows the error and returns zeros:

} catch {
  return { inputTokens: 0, outputTokens: 0, /* ... */ totalTokens: 0, contextLength: 0 };
}

Token widgets then prefer context.tokenMetrics (even when all zeros) and never fall back to context_window totals:

if (context.tokenMetrics) {
  return formatRawOrLabeledValue(item, "In: ", formatTokens(context.tokenMetrics.inputTokens));
}

Expected behavior

  • Token widgets should keep working for multi-hundred-MB / multi-GB transcripts.
  • Prefer streaming / chunked line iteration (or incremental offset cache) instead of materializing the whole file as one string.
  • Failures should not silently look like a real zero-token session if the file is unreadable / unparsable as a whole (optional: leave metrics null so widgets can fall back or hide).

Evidence from a real session

Source Result on ~844MB transcript
Built-in tokens-input/output/total 0 / 0 / 0
session-cost (stdin) correct
Custom streaming statusline parser ReadCache ~23704.1M etc. still correct
Same install on a ~22MB transcript tokens work normally

Suggested fix

  1. Replace full-file readFile + string split in the token-metrics path with a streaming line reader (chunked fs.createReadStream / readline, or fixed-size buffer with newline scanning).
  2. Apply the same approach anywhere else that calls readJsonlLines / readJsonlLinesSync for session-scoped metrics that can grow without bound (speed metrics, session clock, compaction, etc.) so those do not hit the same ceiling later.
  3. Keep the existing stop_reason filtering / compaction boundary logic.

Happy to open a PR with a streaming getTokenMetrics implementation.

Related

This is the same class of problem as any tool that does readFileSync(path, 'utf8') on Claude Code session transcripts after very long / high-cache sessions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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