[BUG] Unexpected session model tokens usage for opus

Status Closed — not planned
Reported on v2.1.143
Maintainer reply None cached
Activity 8 comments · opened May 19, 2026 · closed Jun 19, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Seems that when you use a session with opus-A and running the commands using opus-B they have some complicated relations. Context copying or something. But you are paying for it.
My case was:

When running session on opus47, command on sonnet and subagents on haiku:

Total cost:            $0.96
  Total duration (API):  6m 48s
  Total duration (wall): 4m 4s
  Total code changes:    118 lines added, 0 lines removed
  Usage by model:
     claude-sonnet-4-6:  366 input, 14.0k output, 638.6k cache read, 48.4k cache write ($0.58)
      claude-haiku-4-5:  265 input, 18.1k output, 741.4k cache read, 165.2k cache write ($0.3712)

And this is what I expect to see ^

When running session on opus47, command on opus46, and subagents on haiku:

Total cost:            $2.08
  Total duration (API):  7m 51s
  Total duration (wall): 3m 34s
  Total code changes:    120 lines added, 0 lines removed
  Usage by model:
       claude-opus-4-6:  1.5k input, 6.7k output, 295.6k cache read, 35.5k cache write ($0.54)
      claude-haiku-4-5:  266 input, 19.6k output, 746.1k cache read, 168.3k cache write ($0.3830)
       claude-opus-4-7:  532 input, 8.9k output, 1.0m cache read, 65.9k cache write ($1.15)

basically command run is the most expensive thing here.

When running session on opus46, command on opus47, and subagents on haiku:

Total cost:            $1.58
  Total duration (API):  5m 17s
  Total duration (wall): 2m 27s
  Total code changes:    121 lines added, 0 lines removed
  Usage by model:
       claude-opus-4-7:  528 input, 2.7k output, 108.1k cache read, 72.0k cache write ($0.57)
      claude-haiku-4-5:  253 input, 18.9k output, 715.9k cache read, 160.5k cache write ($0.3671)
       claude-opus-4-6:  356 input, 4.9k output, 447.8k cache read, 46.9k cache write ($0.64)

Same thing here, just cheaper.

This should be fixed or properly explained in the documentation.

What Should Happen?

I should see usages by command and subagents models, not by session model.

Error Messages/Logs

no

Steps to Reproduce

  • make a command running subagents and specify opus-A
  • make subagent using haiku
  • start a session, switch to opus-B
  • run the command
  • cost shows strange numbers for the session model

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.143

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

8 Comments

yurukusa · 4 months ago

This is a clean instance of a broader cost-surface pattern that has accumulated across the issue tracker over the past 48 hours — nine separate issues, all area:cost, all from different reporters, all describing the same general structure: the billing counter and the operator's expected work-product diverge silently.

In your specific case, the divergence is mixed-model overhead. The opus-4.7 ↔ opus-4.6 ↔ haiku path produces ~2.17× the cost of the opus-4.7 ↔ sonnet-4.6 ↔ haiku path for what looks like comparable work — and the existing UI does not expose the multiplier at command-issue time. The decision to invoke a command on opus-4.6 instead of sonnet-4.6 cost you ~$1.12 extra on this single run, with no warning until Total cost printed at the end.

Five operator-side detections that would have caught this before the run completed (and that I sketched in a free synthesis of the broader cluster, here):

  1. Pre-session model declaration — explicit --model enforcement that refuses to invoke a different model for command or subagents unless the cost-multiplier was acknowledged in writing.
  2. Mid-session usage capture — poll claude /usage --json after every N tool calls and compare against the declared model spend; flag drift past a threshold.
  3. Sub-cost classification — break out cache_read vs input vs output vs cache_write per model in real time, since cache_write is where the mixed-model handoff hides most of the cost.
  4. Cost preview at command compose time — for any session that already has K cache tokens loaded, estimate the marginal cost of an opus-4.6 vs sonnet-4.6 invocation before the command runs.
  5. Post-run scorecard — diff actual cost against a pre-declared budget; require explicit override if exceeded.

Your data point ($2.08 mixed-opus path vs $0.96 sonnet path, same workload) is the cleanest single number I've seen for #1 — the cost multiplier is large enough that most operators will accept losing some quality if they see the dollar number at compose time rather than at run end.

A few related cases in the same cluster window (different leak surfaces, same general structure): #60283 (10+ turns of re-reading in-context files with zero output), #60228 (38% → 100% usage gauge spike in 30 seconds with no observable load), #60331 / #60328 / #60223 (/ultrareview decrements free-run quota on crash before findings). The Gist linked above catalogues all nine and maps each to a specific operator-side detection.

If you have a claude /usage --json snapshot from the affected sessions, that's the most useful artifact for vendor-side reproduction.

hurricup · 4 months ago

@yurukusa i don't but i can reproduce and capture any diagnostics devs need.

This is not a question of cost difference between models set. The question is why model not used in the workflow spends anything at all. And this happens only with opus. Probably there is even overhead when session is 4.6 and command 4.6, numbers are growing but hard to say for sure because of normal deviations.

yurukusa · 4 months ago

@hurricup — you're right, my framing was off. The load-bearing observation in your data is the asymmetry between cases A and B, not the gross cost difference. In case A (session=opus-4.7, command=sonnet-4.6) opus-4.7 is absent from the usage block entirely. In case B (session=opus-4.7, command=opus-4.6) opus-4.7 shows 532 input + 8.9k output + 1.0M cache read + 65.9k cache write — more billed than the command model itself. The opus-4.7 line in case B is the entire bug; the opus-4.6 line is what the operator authorised. With opus↔sonnet the session is effectively dormant during command execution; with opus↔opus it isn't.
Three candidate mechanisms that would produce that opus-family-specific asymmetry:

  1. Cross-opus context handoff. When the command model is in the same family as the session model, some part of the harness replays session-model context against the session model to keep state coherent across the handoff. With opus↔sonnet the families diverge and the replay is skipped. Falsifiable: run the same command on opus-4.6 workload with session models at varying context depths (empty / mid / near-compact). If opus-4.7's billed tokens scale with session context size, the mechanism is replay. If roughly constant, it's a fixed supervisor turn (mechanism 3).
  2. Cache topology bleed. opus-4.7's prompt cache is partially reusable by opus-4.6 because the model lineage is close enough for cache structure to overlap, and the harness charges cache reads against both the source-model and the consuming-model bills. With sonnet-4.6 no shared cache topology exists, so no double counting. Falsifiable: in case B, compare the opus-4.7 cache_read token count against the maximum the command model alone could have read during the run. If it exceeds, the cache is double-attributed.
  3. Phantom supervisor turn. Each command invocation inserts an implicit opus-4.7 turn (context preparation, result fold-in) that's only emitted when the command is also opus, because the harness assumes opus-tier oversight is appropriate for opus commands. Falsifiable: tail ~/.claude/projects/*/*.jsonl during case B and look for opus-4.7 entries that aren't tied to a user-issued tool call.

The structural framing for the wider cluster: this is a different leak surface from the nine I catalogued earlier (gauge drift, model selection cost, retry storms, in-context re-reads, etc.). Those are operator-observable at the time the work is happening. Yours is operator-observable only at run-end, in the Usage by model block, and only when the operator already knows which model ran which turn. The remediation belongs at the harness/UI layer — surface the session-model spend per command turn in real time, not as a post-run lump. Worth its own catalog entry; happy to cross-link if you want to extend the constellation.

hurricup · 4 months ago

Tried:

  "env": {
    "ENABLE_PROMPT_CACHING_1H": "1"
  },

no effect

Tried to run command 100% faster than 5 mins, still the same:

Total cost:            $0.70
  Total duration (API):  1m 45s
  Total duration (wall): 1m 50s
  Total code changes:    11 lines added, 0 lines removed
  Usage by model:
       claude-opus-4-6:  361 input, 4.4k output, 231.9k cache read, 44.1k cache write ($0.50)
       claude-opus-4-7:  8 input, 720 output, 92.3k cache read, 21.1k cache write ($0.1959)

So no idea what is this read/write about.

Logs analysis says that opus47 loads something in the end. presumably some opus46 output. but why and why it does not do this with sonnet.

yurukusa · 4 months ago

@hurricup — the 1m45s repro is the data point that narrows the candidate set. With wall-time under the 5-minute cache TTL and ENABLE_PROMPT_CACHING_1H=1 confirmed not to alter the pattern, two of the three mechanisms I proposed earlier are now less likely; the third sharpens into something testable.

Reading your 1m45s usage block more carefully:

opus-4.7:  8 input, 720 output, 92.3k cache read, 21.1k cache write ($0.1959)

The token shape is diagnostic. 8 input tokens means the harness is passing the session model a near-empty fresh prompt. 720 output tokens means the session model is producing a modest response (not a full agentic continuation, not a one-line ack — something in between, roughly the size of a structured summary or a fold-in instruction). 92.3k cache_read on essentially-empty input means the session model is reading 92.3k tokens worth of cached context — almost certainly the command model's output (or the command turn's full transcript) being re-presented to opus-4.7 for ingestion. 21.1k cache_write means opus-4.7 is then materialising a fresh cache entry of comparable size, which is the shape of a summarisation/fold-in turn writing the result back into session memory.

That pattern is structurally consistent with mechanism 3 (Phantom supervisor turn), but with a specific refinement: it's not a pre-command preparation turn, it's a post-command fold-in turn. The session model runs after the command model completes, reads the command's transcript via cache, and writes a synthesised version back into session memory so that the next user turn sees the work as if the session model had done it. With opus↔sonnet the families diverge and the fold-in is either skipped or executed cheaply enough to not show on the usage block; with opus↔opus the lineage is close enough that the harness routes the fold-in through the full session model.

Diagnostic command to confirm. Tail your session JSONL during a fresh opus↔opus reproduction and look for an opus-4.7 turn after the opus-4.6 command turn closes:

SESSION_JSONL=$(ls -t ~/.claude/projects/*/[0-9a-f]*.jsonl | head -1)
jq -r '
  select(.message.role == "assistant" and .message.model)
  | "\(.timestamp) model=\(.message.model) input=\(.message.usage.input_tokens // 0) output=\(.message.usage.output_tokens // 0) cache_read=\(.message.usage.cache_read_input_tokens // 0) cache_write=\(.message.usage.cache_creation_input_tokens // 0)"
' "$SESSION_JSONL" | tail -20

If you see an opus-4.7 entry with the same token shape (small input, small output, large cache_read, smaller cache_write) timestamped after the opus-4.6 entry that corresponds to the command, the post-command fold-in mechanism is confirmed. If the opus-4.7 entry is timestamped before, mechanism 1 (pre-command replay) is back on the table. If you can't find an opus-4.7 entry that maps to either, mechanism 2 (cache topology bleed at the billing layer, not at the inference layer) becomes the working hypothesis.

Implication for the cluster. If post-command fold-in is the mechanism, the operator-side mitigation is uncomfortable but available: avoid same-family session/command pairings until the fold-in either becomes optional or its cost is surfaced before authorisation. The harness-side fix is to either (a) skip the fold-in when the command model's output is small enough to be passed through cheaply, (b) route the fold-in through a smaller model in the same family, or (c) surface the fold-in's projected cost in the command-tool permission prompt so the operator can authorise it explicitly. Today the operator is opted into the fold-in by virtue of choosing the command model; they cannot opt out without changing model families.

For the wider quota-leakage cluster I catalogued earlier, this is a 10th distinct leak surface — the first one where the leakage is family-conditioned rather than triggered by operator action. I'll add it to that gist with cross-reference to this Issue once you confirm or refute the post-command-fold-in hypothesis with the JSONL tail.

The 92.3k cache_read on 8 fresh-input tokens is the cleanest billing-layer signature I've seen for this shape of mechanism. Worth filing as its own diagnostic axis regardless of which of the three candidates is the correct one.

— @yurukusa

github-actions[bot] · 3 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

hurricup · 3 months ago

I'm sorry, how does it work here? I filled the issue with a problem. Which is still there. I see no reaction from devs, so why is it closed for inactivity?

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.