Skip to content

Claude Code context meter confuses cumulative usage with occupancy, shows zero during execution, and obscures compaction #367

Description

@Tryanks

Next-release requirement (2026-09-08): This issue must be completed before the next release. The existing scope, diagnosis and acceptance criteria below remain in force.

Reported symptoms

With Claude Code as the provider and a configured context capacity of 1M tokens:

  • The context display frequently reaches 2.5M, 4.1M or more.
  • During execution it displays zero; a value becomes visible after the turn finishes.
  • It is unclear when automatic compaction happens, what triggers it, and whether the final displayed value is actual current context occupancy at all.

The report concerns trust in the context meter, not account quota. A cumulative processed-token total may legitimately exceed capacity; a current-context display must not conflate those quantities.

Preliminary investigation

Inspected revision: d1b7569aa684460f157d4a374a5729a9cc5d99f0. The following paths are confirmed by source inspection; the reporter's exact 2.5M/4.1M run has not been captured or replayed, and no GUI reproduction or fix was performed.

1. Result-level aggregate usage is stored as current context occupancy.

claude::map_usage sums input_tokens + cache_read_input_tokens + cache_creation_input_tokens + output_tokens into TokenUsage.used_tokens. on_result calls that same mapper on the result's usage. However, the canonical field is documented as “Total context currently in use,” and the UI context meter reads it directly. Claude Code's result usage covers the turn's main-loop requests, whereas current context must describe an individual/latest request or an explicit context snapshot. Repeated requests can process the same cached context many times: summing that traffic explains multi-million-token values without a multi-million-token window.

The adapter separately stamps total_processed_tokens, and the UI already has a separate “Total processed” row. This is a semantic mapping problem, not a request to cap or hide that legitimate cumulative total. The percentage is clamped to 100%, but that cannot correct the meaning of the raw count.

2. Streaming usage loses information, and unknown is rendered as zero.

on_stream_event(message_start) retains only the message ID and ignores message.usage. message_delta maps its usage independently with no modelUsage; Timeline::apply(TokenUsage) replaces the entire previous usage record. A partial update can therefore erase input/cache counts, capacity and cumulative totals. Official stream examples include output-only usage deltas; they must not be interpreted as a complete context snapshot.

context_meter::format_tokens(None) explicitly returns "0". Before usage arrives, an unknown value therefore looks like a measured empty context. After a result arrives, a more complete-looking record returns, but its occupancy field still contains the aggregate described above. Do not assume every run's zero has the same event sequence; capture the actual stream.

The composer snapshot only constrains an already reported capacity to the selected context setting; it does not recover missing live usage or turn aggregate counts into occupancy.

3. Compaction is surfaced without its metadata or occupancy lifecycle.

The adapter converts system/compact_boundary to a payload-free ContextCompacted, discarding compact_metadata. The timeline appends a divider but does not establish a fresh post-compaction usage snapshot. The UI's >90% warning is a presentation threshold, not evidence of the harness's actual auto-compaction trigger.

Requested behavior

  • Separate current context occupancy/capacity, per-request or per-turn usage, and lifetime processed totals in the provider contract. Document the provenance and scope of every displayed number.
  • Derive occupancy from the latest main-conversation usage/context observation, including fresh input and cache components as appropriate to the provider contract. Do not sum repeated API requests or subagent contexts into main-context occupancy.
  • Preserve and merge partial streaming fields correctly within a message/request; replace per-request state at the correct boundary. Avoid double-counting repeated assistant message IDs and cumulative output deltas.
  • Show useful live updates when available. Otherwise display an explicit unknown/updating or last-known state with freshness, never an invented zero or fabricated precision. Keep capacity distinct from measured occupancy.
  • Result-level accounting must not overwrite a valid current-context snapshot with turn aggregates. Do not fix this by merely clamping counts to 1M.
  • Preserve observable compaction metadata, such as trigger and pre-compaction token count when supplied. Distinguish compaction in progress/completed and unknown post-compaction occupancy. Show an exact threshold only when the harness actually exposes it; do not infer it from the warning color or nominal capacity.
  • Check model changes, reduced/custom capacities, resume/replay, cancellations, missing usage and subagent routing. Keep accounting scope correct across these cases.

Validation required for implementation

Capture a redacted Claude Code stream including a multi-step tool turn, message-start usage, partial message-delta usage, result aggregates and a compaction boundary. Replay through the production adapter → timeline → composer snapshot path.

Acceptance cases:

  1. Multiple requests each below 1M may have a turn aggregate above 1M; the meter still shows the latest request's context and “Total processed” remains separately meaningful.
  2. An output-only update preserves known input/cache/capacity rather than replacing them with missing values. Missing data renders as unknown, not measured zero.
  3. First response, in-flight execution and turn completion use consistent semantics. Completion does not jump from current occupancy to aggregate traffic.
  4. A compaction event reports available metadata, invalidates or marks stale occupancy appropriately, and adopts the next valid observation without claiming a known zero immediately after compaction.
  5. Repeated message IDs, subagent messages, restored history and cancelled turns do not inflate or misattribute occupancy.

Use literal provider fixtures with independently derived expectations. The existing result_maps_to_turn_completed_with_usage test asserts that aggregate input/cache/output becomes used_tokens; revise that outdated expectation while preserving legitimate accounting coverage. Update the UI design contract and both locale files for any new labels. Record actual GUI evidence as well as adapter/state tests.

References

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