Skip to content

Persisted token usage is always empty — breakdown rides on the prompt response (result.usage), not usage_update._meta.usage #406

Description

@odrobnik

Summary

A session's persisted cumulative_token_usage (in ~/.acpx/sessions/<id>.json) — and therefore getStatus().usage.cumulative — is always {}, even though the agent reports a full per-turn token breakdown every turn. This affects every ACP agent I checked: claude-agent-acp, codex-acp, and cursor-agent. cumulative_cost is captured fine; only the token breakdown is lost.

Root cause

The conversation reducer captures token usage only from usage_update._meta.usage:

  • src/session/conversation-model.tsusageToTokenUsage() reads update._meta?.usage (falling back to top-level input_tokens/…).

But none of the agents put the breakdown there. They emit usage_update as { used, size } (claude adds cost on the final one), and report the actual token breakdown on the session/prompt response under result.usage:

  • claude-agent-acp: result.usage = { inputTokens, outputTokens, cachedReadTokens, cachedWriteTokens, totalTokens }
  • codex-acp: result.usage = { inputTokens, outputTokens, cachedReadTokens, thoughtTokens, totalTokens } (plus _meta.quota detail)
  • cursor-agent: response schema is { stopReason, usage: { inputTokens, outputTokens, cachedReadTokens, cachedWriteTokens, … } }

The data is already available to the runtime — src/cli/output/output.ts consumes it for the printed usage line:

const usageLine = this.formatUsageLine(asRecord(result.usage));

…but result.usage is never folded into the conversation's cumulative_token_usage / request_token_usage. So the persisted record (and getStatus()) shows nothing, while the CLI's own usage line shows the real numbers.

Reproduction

  1. Run any prompt with claude-agent-acp, codex-acp, or cursor-agent.
  2. Inspect ~/.acpx/sessions/<id>.jsoncumulative_token_usage is {} (note cumulative_cost is populated for claude).
  3. The session's <id>.stream.ndjson confirms every usage_update is { used, size } (no _meta.usage), while the session/prompt response carries the breakdown under result.usage.

Suggested fix

Fold the prompt response's result.usage into cumulative_token_usage / request_token_usage (mapping cachedReadTokenscache_read_input_tokens, cachedWriteTokenscache_creation_input_tokens, thoughtTokensthought_tokens) — the same result.usage the output line already reads. The existing usage_update._meta.usage path can stay for agents that populate it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal priority bug or improvement with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:data-lossThis issue is about lost, corrupted, or silently dropped user/session/config data.impact:session-stateThis issue is about session, memory, transcript, context, or agent state drift.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExempts this issue from stale automation.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions