Cost double-counts cache_creation tokens (billed at both input and cache_write rates) for Anthropic models
Version / commit: main @ 4f7ccfd (pricing.snapshot.yaml fetched_at: 2026-05-21)
Summary
For Anthropic models, computeCost bills cache_creation (cache-write) tokens
twice: once at the full input rate and once at the cache_write rate. This
over-estimates USD cost for any call that creates cache (i.e. most first-turn or
large-context calls). The behavior also contradicts the token mapping documented in
pricing.snapshot.yaml itself.
Root cause
gen_ai.usage.input_tokens for Anthropic already includes both cache-read and
cache-creation tokens — as your own snapshot header states:
# gen_ai.usage.input_tokens -> input + cache_read + cache_creation (Anthropic)
# gen_ai.usage.cache_creation.input_tokens -> cache_creation (Anthropic only; subtract too)
But normalizeSpan subtracts only cache_read, never cache_creation:
src/otel/parser.ts:123
const freshInput = Math.max(rawInput - cacheRead, 0); // cache_creation NOT subtracted
It then calls computeCost with:
src/otel/parser.ts:138
computeCost({ input: freshInput + cacheRead + cacheCreation, cache_read: cacheRead, cache_write: cacheCreation, output }, price)
Inside computeCost (src/pricing/loader.ts):
const fresh = Math.max(totalInput - cacheRead - cacheWrite, 0);
// totalInput = freshInput + cacheRead + cacheCreation
// => fresh = freshInput = rawInput - cacheRead <-- still contains cache_creation
return (fresh/1e6)*input
+ (cacheRead/1e6)*cached_input
+ (cacheWrite/1e6)*cache_write // cacheWrite = cacheCreation
+ (output/1e6)*output;
So cache_creation sits inside fresh (billed at the input rate) and is billed
again as cacheWrite (at the cache_write rate). Net overcharge per call =
cache_creation × input_rate.
Reproduction (self-contained, using your own snapshot prices)
One claude-opus-4.5 call (input 5, cached_input 0.5, cache_write 6.25,
output 25 per 1M tokens):
input_tokens = 20000, cache_read = 0, cache_creation = 19000, output = 200
- True fresh input =
20000 − 0 − 19000 = 1000
|
fresh billed @ input rate |
cost |
| copilot-cost (current) |
20000 |
$0.22375 |
| correct |
1000 |
$0.12875 |
Overcharge = $0.095 = 19000 × $5 / 1M = cache_creation × input_rate.
I also validated this against real Copilot CLI sessions, where GitHub's own billed
amount is emitted as github.copilot.nano_aiu (USD = nano_aiu / 1e11). On a full
multi-model run, the corrected formula matches GitHub's billed total to the cent,
while the current formula over-reports the cost by ~35%.
Suggested fix (one line)
Subtract cache_creation in freshInput, matching the documented mapping:
src/otel/parser.ts:123
const freshInput = Math.max(rawInput - cacheRead - cacheCreation, 0);
This corrects the cost (the input argument to computeCost becomes rawInput, so
fresh resolves to rawInput − cacheRead − cacheCreation) and also makes the
reported input_tokens the true fresh input. It is a no-op for OpenAI/Google models,
where cache_creation is 0.
Cost double-counts
cache_creationtokens (billed at bothinputandcache_writerates) for Anthropic modelsVersion / commit:
main@4f7ccfd(pricing.snapshot.yamlfetched_at: 2026-05-21)Summary
For Anthropic models,
computeCostbillscache_creation(cache-write) tokenstwice: once at the full
inputrate and once at thecache_writerate. Thisover-estimates USD cost for any call that creates cache (i.e. most first-turn or
large-context calls). The behavior also contradicts the token mapping documented in
pricing.snapshot.yamlitself.Root cause
gen_ai.usage.input_tokensfor Anthropic already includes both cache-read andcache-creation tokens — as your own snapshot header states:
But
normalizeSpansubtracts onlycache_read, nevercache_creation:src/otel/parser.ts:123It then calls
computeCostwith:src/otel/parser.ts:138Inside
computeCost(src/pricing/loader.ts):So
cache_creationsits insidefresh(billed at theinputrate) and is billedagain as
cacheWrite(at thecache_writerate). Net overcharge per call =cache_creation × input_rate.Reproduction (self-contained, using your own snapshot prices)
One
claude-opus-4.5call (input 5,cached_input 0.5,cache_write 6.25,output 25per 1M tokens):input_tokens = 20000,cache_read = 0,cache_creation = 19000,output = 20020000 − 0 − 19000 = 1000Overcharge =
$0.095 = 19000 × $5 / 1M=cache_creation × input_rate.I also validated this against real Copilot CLI sessions, where GitHub's own billed
amount is emitted as
github.copilot.nano_aiu(USD =nano_aiu / 1e11). On a fullmulti-model run, the corrected formula matches GitHub's billed total to the cent,
while the current formula over-reports the cost by ~35%.
Suggested fix (one line)
Subtract
cache_creationinfreshInput, matching the documented mapping:src/otel/parser.ts:123This corrects the cost (the
inputargument tocomputeCostbecomesrawInput, sofreshresolves torawInput − cacheRead − cacheCreation) and also makes thereported
input_tokensthe true fresh input. It is a no-op for OpenAI/Google models,where
cache_creationis 0.