[FEATURE] Per-model cost attribution in /usage and at /model switch time
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code shows me that my quota is draining, but never which model is draining it. Those are
different questions, and only the second one is actionable.
A single /model switch, made with no cost signal at the moment of switching, turned out to account for
56% of my last two weeks of spend. I only found that by writing my own aggregator over~/.claude/projects/**/*.jsonl. There was no in-product path to that answer.
The sequence, which I suspect is common:
- Hit a hard problem, switch to a more capable model with
/model. - It solves the problem. The switch persists as the session default.
- Keep working. Unrelated follow-up tasks now run on the premium model, because nothing marks it as
active in a way that nags.
- Two weeks later, quota is draining much faster with no change in actual workload, and no visible
explanation.
Measured from my own logs (deduplicated by requestId, effective rates derived from Claude Code's own~/.claude.json lastModelUsage[].costUSD divided by that entry's raw tokens):
model reqs raw tok medianCtx p90Ctx est $
fable-5 13,068 4.51B 301,850 642,331 6,502
opus-5 40,305 7.88B 153,363 380,538 4,883
sonnet-5 4,208 418M 93,807 157,149 217
--------
14-day total 11,653
Fable was 23% of requests but 56% of spend. Blended effective rate per million raw tokens, across all
token types, from the same source:
fable-5 1.44 <- 2.3x opus-5
opus-5[1m] 0.98 <- 1.6x opus-5, from the context variant alone
opus-5 0.62
sonnet-5 0.52
haiku-4-5 0.45
There is a second, quieter version of the same blind spot. Setting a long-context model variant as a
persistent default in settings.json cost me roughly 1.6x per token, while my median session start
was 54k tokens and p90 was 63k. I was paying a long-context premium on every session and effectively
never using the window. Because it lives in a config file rather than the session UI, nothing ever
reminded me it was on. It had been set for weeks.
Underlying reason this compounds so hard: my weighted cost split for the period was 71% cache reads,
16% cache writes, 12% output. Cost is dominated by re-reading context every turn, so premium model x
large context multiplies in a way no single number currently on screen reveals. One session ran at a
median of 503,347 tokens of context per request, peaking at 998,632.
Proposed Solution
Expose per-model cost attribution, on the three surfaces where the decision actually gets made:
- At switch time. When
/modelmoves to a model that bills at a different rate, print one line with
the approximate multiplier versus the model being left. Not a confirmation prompt, not a warning
dialog, just a visible number at the moment of the decision.
````
> /model fable
Switched to Fable 5. Roughly 2.3x the per-token rate of Opus 5.
- In
/usage. Add a per-model split to whatever window is displayed. This is the single highest
value piece: it turns "quota is draining" into "quota is draining because of this", and would have
surfaced my problem on day one instead of day fourteen.
````
7-day window: 68% used
fable-5 56% ####################
opus-5 41% ###############
sonnet-5 3% #
- While it stays active. A persistent indicator in the status line or session header when a premium
model or a long-context variant is in effect. The current design makes an expensive default invisible
precisely because it is a default, and config-file settings are the easiest to forget.
All three are the same feature: making model cost attributable. Item 2 alone would have caught this.
Alternative Solutions
- Write your own aggregator over the transcripts. What I ended up doing. It works, but it requires
knowing that per-model usage is recoverable at all, and it has a trap in it (see Additional Context).
Not a reasonable ask of most users.
- Third-party usage tools. These exist, but shipping the number in
/usageis strictly better since
Claude Code already has authoritative rate-limit data.
- Just be disciplined about switching back. This is what I will now do, but it relies entirely on
memory, with no feedback loop. The failure mode is silent and takes two weeks to notice.
Feature Category
Configuration and settings
Use Case Example
- I'm deep in a hard debugging session on the default model and it stalls.
- I run
/model <premium>. Today: nothing printed about cost. Proposed: one line telling me it is ~2.3x. - The premium model solves it. I move on to unrelated, much easier follow-up work in the same session.
- Today: the premium model silently stays active across all of it. Proposed: a status line indicator
keeps it visible, so switching back is a thing I actually remember to do.
- Next morning I run
/usage. Today: I see a percentage and cannot act on it. Proposed: I see that the
premium model is 56% of the window despite being a minority of my requests, and I switch back
immediately.
Step 5 is the whole request. The information already exists; it just is not attributed.
Additional Context
Environment: Claude Code CLI on macOS, Max plan, default model set via settings.json and switched
per-session with /model, roughly 15 active repos.
One technical note for anyone implementing or verifying this, which cost me a wrong number on the first
pass. An assistant message with N content blocks is written as N rows in the session .jsonl, each
carrying the same cumulative usage object. One message in my logs was emitted as 12 rows (2thinking, 10 tool_use), all with output_tokens=3620 and an identical requestId. Summing usage
across rows therefore overcounts by roughly 2x. I reported 25.5B tokens to myself before catching it and
correcting to 12.9B.
That serialization is reasonable, but it is undocumented as far as I can tell, and it means any
transcript-derived usage number is suspect unless it deduplicates by requestId or message id. If the
per-model breakdown ships natively, this trap mostly stops mattering, which is another argument for
shipping it. I am happy to file that as a separate documentation issue if it would be useful.