Skip to content

Claude usage cost shows "Unavailable": Claude 5 family missing from the pricing table #303

Closed

Description

@florianbehrend

Symptom

Claude usage shows "Unavailable" / $— instead of a cost, for every current Claude Code user. The token counts are correct; only the cost is missing.

Root cause

claude-opus-5 and claude-sonnet-5 are absent from ModelPricing.table (Sources/PokeTokenBar/Core/ModelPricing.swift).

Chain:

  1. Claude Code *.jsonl records carry no explicit cost field, so Entry.explicitCost is nil.
  2. ModelPricing.estimatedCost(model: "claude-opus-5", …) returns nil — no table row, and the family fallback no longer exists.
  3. LocalUsageReader.Bucket.add takes the last branch and merges .unavailable into costCoverage.
  4. UsageCost.text hits coverage.unknown && !coverage.hasKnown and renders l.costUnavailable.

Because the Claude 5 family is the current generation, effectively all Claude usage lands on that branch — the coverage is unknown-only, not partial, so no amount is shown at all.

Measured on a real profile: 12,492 of 13,318 "model" occurrences over the last 7 days were claude-opus-5; for the current day it was 417 of 417.

Regression origin

Before #289 (fix: distinguish estimated and unavailable usage costs, shipped in v2.5.4), ModelPricing.rate(for:) had a model-family fallback:

if m.contains("opus")   { return .perMillion(5, 25, 6.25, 0.5) }

claude-opus-5 was priced solely through that fallback. #289 removed the fallback — correct in itself, since #277 showed the fallback mispriced Claude Fable 5.1 by 4× — but no exact rows were added for the Claude 5 family to replace it.

This was foreseen and written down. docs/reference/defect-log.md:217:

폴백이 우연히 맞는 모델(예: claude-opus-5 → opus 폴백)은 건드리지 않는다

The guidance was to add exact rows whenever the fallback stops covering a model. The fallback was removed without that step.

Why no test caught it

  • ModelPricingTests.testCurrentOpenAIStandardRates asserts GPT rows only. Nothing asserts that the Claude models actually read from logs are priced.
  • ModelPricingTests.testUnknownNamesNeverBorrowFamilyPrices encodes "unknown model → nil" as correct behaviour. claude-opus-5 silently joined that class, so the suite confirms the regression instead of reporting it.
  • Every other test uses models that are in the table (claude-fable-5-1, claude-opus-4-8, claude-sonnet-4-6, gemini-2.5-pro), so all of them pass.
  • Line coverage does not help: the .unavailable branch is executed by existing tests. This is the "coverage number is not evidence" case from CLAUDE.md.

Class sweep

Models observed across local provider logs that currently resolve to nil:

Model Priced? Note
claude-opus-5 no current default model
claude-sonnet-5 no present in logs
gemini-3-pro no separate question, not covered here
codex-auto-review no not a real model id, correct as-is

The whole Claude 5 family is missing; the remaining Claude rows (claude-opus-4-8, claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5-20251001, claude-fable-5, claude-fable-5-1) were verified against the official pricing page and are correct.

Fix

Add exact rows, taken from https://platform.claude.com/docs/en/about-claude/pricing (base input / 5m cache write / cache read / output):

Model Input Output 5m cache write Cache read
Claude Opus 5 $5 / MTok $25 / MTok $6.25 / MTok $0.50 / MTok
Claude Sonnet 5 $2 / MTok $10 / MTok $2.50 / MTok $0.20 / MTok
"claude-opus-5":   .perMillion(5, 25, 6.25, 0.5),
"claude-sonnet-5": .perMillion(2, 10, 2.5, 0.2),

Permanent capture

A row-by-row table test is not enough — it would have been written against the same stale list. Add a regression test that asserts the currently shipping model identities resolve to a non-nil estimate, i.e. the inverse of testUnknownNamesNeverBorrowFamilyPrices. That test fails the moment a new generation ships unpriced, which is exactly the trigger condition here, and it is the guard the removal in #289 lacked.

Also record the class in docs/reference/defect-log.md: removing a fallback is a behaviour change for every input the fallback used to serve, and those inputs must be enumerated before the fallback goes.

Activity

  1. florianbehrend commented on Sep 17, 2026

    @florianbehrend
    ContributorAuthor

    Correction to this issue. @DenisSeller points out in #318 that the claim above — that Claude Code *.jsonl records carry no explicit cost field — is wrong, and reports cumulative type:"cost-state" records with per-model costUSD under modelUsage, present across all 128 sessions sampled.

    I went and checked rather than just taking the correction, and the picture is genuinely split. On my machine, across all 93 .jsonl files under ~/.claude/projects (not just recent ones), there are zero occurrences of cost-state, modelUsage, costUSD, or hasUnknownModelCost. So the observation behind this issue was accurate for the data in front of me — but I stated it as a property of the format, and it clearly isn't one. Something (client version, entrypoint, or a setting) decides whether the ledger is written, and I don't know which.

    The accurate version: where no cost ledger is present, Claude usage can only be priced from the table, and a missing row renders as unavailable. That's the condition this issue actually describes, and it's the condition on my install.

    Two consequences worth recording:

    If anyone can pin down what makes the ledger appear or not, that's probably the most useful next data point — it decides whether the table is a fallback for a rare case or the primary path for most users.

  2. added a commit that references this issue on Sep 18, 2026
    1ef1979
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