Managed modelPricing in the cost estimator: three layers, one number
Issue #3878, second half. #3885 pinned the vocab to the binary's price tiers. This change makes the estimator honour the org override CC itself honours, so an org on a contract sees the same figure in /ork:analytics as in /cost.
What CC 2.1.258 does with modelPricing (read from the binary)
| field | rule |
|---|---|
multiplier | number in (0, 1]; "scales every computed cost, overridden or not (0.85 = 85% of the price)" |
overrides[modelId] | { input, output, cacheRead, cacheWrite }, all four required, each 0 to 10000, USD per million tokens; cacheWrite prices both the 5-minute and the 1-hour write |
| key matching | case-insensitive; a built-in ID covers its dated and provider forms; the earlier row wins a duplicate |
| bad input | "An invalid row or multiplier is reported and skipped; the rest still apply" |
| sources | managed only: server-managed, MDM/OS policy, or managed-settings.json; ignored in user, project, local and --settings |
Paths: /Library/Application Support/ClaudeCode/managed-settings.json (macOS), /etc/claude-code/managed-settings.json (Linux, WSL), C:\Program Files\ClaudeCode\managed-settings.json (Windows).
Precedence, lowest to highest
models.vocab.json < ~/.claude/orchestkit-pricing.json < managed modelPricing.overrides
then x modelPricing.multiplier on every rate
Layers 1 and 2 already existed. Layer 3 and the multiplier are new. Try them: the table below is the estimator's getCostConfig().models for the current Fable, computed the same way the TypeScript does it.
| claude-fable-5-1 | input | output | cache read | cache write | 1M in + 1M out |
|---|---|---|---|---|---|
| vocab (list, layer 1) | 10 | 50 | 0.25 | 12.5 | $60.00 |
| effective |
What the tests pin
| case | expected |
|---|---|
no managed file, or a file without modelPricing | list price, no stderr |
| multiplier only (0.85) | every rate of every vocab model x0.85 |
| override for a known model | that row replaced; alias and [1m] variant follow; other rows at list |
| override for an unknown model | priced by the row instead of the sonnet fallback |
| multiplier 1.5, 0, -0.2, "abc", null | ignored, one stderr line, overrides in the same file still apply |
| malformed JSON, non-object block, incomplete or >10000 row | skipped, one stderr line per process, never a throw |
| managed override vs orchestkit-pricing.json vs vocab | managed wins, user file second, multiplier last on all three |
| the 12 existing #2338 canaries, incl. the per-model cache-rate exception | unchanged, now hermetic (the machine's real managed file can no longer leak in) |
Still open on #3878 after this change
managed-settings.d/ drop-ins, MDM and server-managed policy sources, and the host-application fallback are not read; those users still see list price here. The optional local pricing canary (claude -p --output-format json reproduced to the cent, real API call, never in CI) is not built.