Skip to content

[Bug]: Transient Claude thinking-token counters occupy 38% of provider-log bytes #19777

Open

Description

@josephv123

Before submitting

  • Searched existing issues and PRs for thinking_tokens and provider-event logging.
  • Included a source-level reproduction and measured log totals.

Area

apps/server

Steps to reproduce

  1. Run the Claude provider with thinking enabled and inspect logs/provider/events.*.log* without changing them.
  2. Count lines and bytes containing the exact substring "subtype":"thinking_tokens".
  3. Exercise the exported logging predicate from a checkout with dependencies installed:
import { shouldPersistProviderEvent } from './apps/server/src/provider/EventNdjsonLogger.ts';
const decoded = payload => ({ event: { stage: 'decoded', payload } });
console.log(shouldPersistProviderEvent('native', decoded({
  type: 'system', subtype: 'thinking_tokens',
  estimated_tokens: 100, estimated_tokens_delta: 1,
}))); // true
console.log(shouldPersistProviderEvent('native', decoded({
  type: 'stream_event', event: { type: 'content_block_delta',
    delta: { type: 'thinking_delta', thinking: 'x' } },
}))); // false

Verified with node --input-type=module on Node 26.10.0. This reproduction calls the production predicate and uses synthetic frames.

Expected behavior

Per-token counter telemetry receives the same filtering or sampling treatment as other transient token frames, while terminal usage and diagnostic records remain available.

Actual behavior and evidence

The logger retains every system/thinking_tokens frame. A read-only recount on one Linux installation on October 15 gave:

All provider records Thinking-token counters
Lines 430,306 299,160 (69.52%)
Bytes 444,437,103 170,979,370 (38.47%)

These are serialized log bytes, not model token counts. An earlier retained Mac audit independently counted 59,928 such lines of 80,917 and 33.5 MB of 70.7 MB; that historical Mac sample was not re-collected in this pass.

shouldPersistProviderEvent filters other transient frames but has no rule for these counters. The write path then serializes them and appends them to the same bounded log used for useful protocol history. At the existing 512 MiB total retention limit, these records consume a substantial part of that history budget. Logging is already asynchronous; this report does not claim event-loop stalls or a broken retention cap.

Recount with Python, without printing conversation contents:

from pathlib import Path
total_lines = total_bytes = counter_lines = counter_bytes = 0
for path in (Path.home() / '.t3/userdata/logs/provider').glob('events.*.log*'):
    with path.open('rb') as stream:
        for line in stream:
            total_lines += 1
            total_bytes += len(line)
            if b'"subtype":"thinking_tokens"' in line:
                counter_lines += 1
                counter_bytes += len(line)
print(total_lines, total_bytes, counter_lines, counter_bytes)

Impact / version / environment

Minor storage and diagnostic-retention overhead. Ubuntu 26.04, Claude provider, 0.0.46-nightly.20261014.3132. Predicate verified again on main e536bfd33bc3c351997141d93790df75eeded7b9; relevant code is unchanged from the original audit at ec1313c8f8333292506f05a355bb72d308a455d8. Related #19430 fixes the asynchronous sink, a different problem.

Activity

  1. juliusmarminge commented on Oct 15, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Accepting this as a low-priority log-retention bug. On main (e536bfd), shouldPersistProviderEvent already drops per-token native frames (raw frames, stream_event/content_block_delta, message.part.delta, and the transient native methods), but it has no rule for Claude's system/thinking_tokens counters, so every one of them is written into the bounded 512 MiB provider log.

    These counters don't look like something we keep on purpose for diagnostics. Thinking-token usage is tracked at runtime (ClaudeTurnTokenUsage.ts), and the final usage and result records still get logged. So dropping the per-delta counters wouldn't lose anything the other transient-frame filters don't already give up. This is separate from #19430 (the async sink) and from #15396 (how thinking tokens are reported in usage).

    A small PR is welcome: return false for decoded native system/thinking_tokens frames in shouldPersistProviderEvent, and add a test next to the existing transient-frame cases. Please keep terminal usage and result records persisted.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 15, 2026
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

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions