You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Independent reproduction, with a reconciliation against the provider's actual billing and a control group that isolates the cause to the missing cache.write count.
Setup: opencode 1.18.5, macOS, opencode-go provider (@ai-sdk/openai-compatible), headless opencode run --format json, no plugins, isolated XDG_* dirs. 63 automated sessions on 2026-07-31 across three models (21 each).
Result for gpt-5.6-luna: summing opencode's self-reported per-step cost gives $0.1459. The provider's own per-model daily billing view shows $0.41 for the same model on the same day — a 2.8× understatement.
Token totals opencode recorded across those 21 luna sessions:
The gap is $0.2641. At luna's $0.125/M cache-write rate that implies ~2.11M unreported cache-write tokens, against 3.81M recorded reads — a ~1.8:1 read:write ratio, which is what you'd expect from sessions with growing context. (As #36749 puts it: something had to write the prefix that is being read.)
Control group — same gateway, same day, same harness:
model
cache-write rate
opencode self-reported
provider billing UI
gpt-5.6-luna
$0.125/M
$0.1459
$0.41 ✗
deepseek-v4-flash
none
$0.1138
$0.12 ✓
mimo-v2.5
none
$0.0882
$0.11 ✓
This is the part I think is useful for triage: the two models that don't bill for cache writes reconcile with billing to the cent, and only the model that does is wrong. So this isn't general cost drift or a gateway-wide pricing mismatch — the error tracks exactly with whether cache writes are billable, which points at the missing cache.write count rather than at any rate.
Blast radius on this gateway is wider than luna: qwen3.5-plus, qwen3.6-plus, qwen3.7-plus and qwen3.7-max all carry cache-write rates as well, with qwen3.7-max at $3.125/M — above its own $2.50/M input rate.
Related: #39891 reports the same luna discrepancy via the TUI-vs-web comparison, and #36749 has the root cause on this code path (@ai-sdk/openai-compatible hardcodes cacheWrite: void 0, and usage() only reads inputTokenDetails.cacheWriteTokens) along with an offered fix.
Both fields are Schema.optional on input_tokens_details. When a route sends cached_tokens but no cache_write_tokens, cacheWrite is undefined, so it contributes nothing to the subtraction and
the tokens that were written to cache stay inside nonCached. Downstream, SessionUsage.tokens
coerces the undefined write to 0 via safe(), and the same tokens are already sitting in input.
So cache.write = 0 isn't a dropped field. It's a misclassification, and it's silent because the
arithmetic still balances.
The natural experiment
My database has both kinds of route side by side, which makes this easy to check. Average input per
request, for every model with at least 250 requests that reported cache.read > 0:
provider / model
requests
avg input
avg cache.write
anthropic/claude-opus-5
4,634
2
16,018
openai/astra
4,984
3
6,301
opencode-go/gpt-5.6-luna
276
3
81,910
openai/gpt-5.6-sol
13,179
4,860
0
openai/gpt-5.6-terra
11,955
6,231
0
openai/gpt-5.6-luna
51,202
21,065
0
Every route that reports a cache write has an average input of two to three tokens. Every route that
doesn't carries thousands. That's the same quantity showing up in a different column, not two
different behaviours.
The obvious alternative — that these models simply miss cache more often, so the extra input is
genuinely uncached content rather than an unlabelled write — is ruled out by the filter. Every row in
these tables had cache.read > 0, and you cannot read from a cache that nothing wrote. Caching is
demonstrably active on exactly the requests reporting zero writes. The only remaining reading is that
the writes happened and weren't labelled.
gpt-5.6-luna is the cleanest pair: on opencode-go it reports 81,910 average write and 3 average
input; on openai it reports 0 write and 21,065 average input. Same model, same account.
How much
Because it's a reclassification rather than a loss, this can be measured directly instead of
extrapolated. Summing input over requests that also had cache.read > 0:
provider / model
requests
input recorded
cache.read
share of prompt volume filed as input
openai/gpt-5.6-luna
51,202
1,078,547,903
3.62B
22.9%
openai/gpt-5.6-terra
11,955
74,492,643
1.12B
6.2%
openai/gpt-5.6-sol
13,179
64,044,087
1.61B
3.8%
openai/astra
4,984
14,952
0.38B
0.0%
anthropic/claude-opus-5
4,634
9,268
0.93B
0.0%
That's 1.217 billion tokens on the openai provider alone that would have been booked as cache.write on a reporting route. The two reporting models round to 0.0%.
The billing consequence follows from that: those tokens are priced at the input rate rather than the
cache-write rate, so the cost is understated by the write premium on 1.2B tokens rather than being
wrong by 100%. That is probably why the discrepancy showed up as a surprise on the invoice rather than
as an obviously broken number.
Same signature elsewhere
The pattern isn't OpenAI-specific — it's every route that omits the field:
provider / model
requests
avg input
avg cache.write
xai/grok-4.6
5,015
10,414
0
xai/grok-4.3
859
8,919
0
opencode-go/minimax-m3
9,028
3,288
0
opencode-go/glm-5.2
253
13,015
0
xai is the most absolute case: 5,892 assistant messages, max(cache.write) and sum(cache.write)
both exactly zero, across two models. If a regression test is wanted, that's the cleanest input.
What I can't tell from here
Whether the openai route omits cache_write_tokens because the API doesn't send it, or because it
arrives under a different key that the schema at open-responses.ts:182-193
doesn't look for. That's answerable without new instrumentation, though — mapUsage already stows the
raw payload as providerMetadata: { [providerMetadataKey]: usage }, so one captured response.completed from a gpt-5.6-* request would settle it.
If the field genuinely isn't available on that route, the honest fix is probably not to invent it but
to stop the silent reclassification: either surface non-cached input as an explicit "uncached, may
include writes" quantity, or price it at the write rate when caching is known to be active. Reporting 0 for a quantity that is demonstrably non-zero is the part that costs users money quietly.
Activity
github-actions commentedon Jul 19, 2026
This issue might be a duplicate of existing issues. Please check:
rekram1-node commentedon Jul 20, 2026
are u using openai provider? What provider are u using? Need more info, are u using plugins? Etc etc
dalemartyn commentedon Jul 20, 2026
Yeah, I'm using the normal openai provider, signed in with a subscription (chat got plus). No plugins, vanilla install.
johannes-engler-mw commentedon Aug 2, 2026
Independent reproduction, with a reconciliation against the provider's actual billing and a control group that isolates the cause to the missing
cache.writecount.Setup: opencode 1.18.5, macOS,
opencode-goprovider (@ai-sdk/openai-compatible), headlessopencode run --format json, no plugins, isolatedXDG_*dirs. 63 automated sessions on 2026-07-31 across three models (21 each).Result for
gpt-5.6-luna: summing opencode's self-reported per-stepcostgives $0.1459. The provider's own per-model daily billing view shows $0.41 for the same model on the same day — a 2.8× understatement.Token totals opencode recorded across those 21 luna sessions:
The gap is $0.2641. At luna's $0.125/M cache-write rate that implies ~2.11M unreported cache-write tokens, against 3.81M recorded reads — a ~1.8:1 read:write ratio, which is what you'd expect from sessions with growing context. (As #36749 puts it: something had to write the prefix that is being read.)
Control group — same gateway, same day, same harness:
gpt-5.6-lunadeepseek-v4-flashmimo-v2.5This is the part I think is useful for triage: the two models that don't bill for cache writes reconcile with billing to the cent, and only the model that does is wrong. So this isn't general cost drift or a gateway-wide pricing mismatch — the error tracks exactly with whether cache writes are billable, which points at the missing
cache.writecount rather than at any rate.Blast radius on this gateway is wider than luna:
qwen3.5-plus,qwen3.6-plus,qwen3.7-plusandqwen3.7-maxall carry cache-write rates as well, withqwen3.7-maxat $3.125/M — above its own $2.50/M input rate.Related: #39891 reports the same luna discrepancy via the TUI-vs-web comparison, and #36749 has the root cause on this code path (
@ai-sdk/openai-compatiblehardcodescacheWrite: void 0, andusage()only readsinputTokenDetails.cacheWriteTokens) along with an offered fix.Zaczero commentedon Aug 18, 2026
I think I have the mechanism for this, and the short version is that the write tokens aren't missing —
they're being recorded as
input.Where it happens
mapUsagederives the non-cached portion by subtraction:opencode/packages/ai/src/protocols/open-responses.ts
Lines 593 to 609 in 39be159
Both fields are
Schema.optionaloninput_tokens_details. When a route sendscached_tokensbut nocache_write_tokens,cacheWriteisundefined, so it contributes nothing to the subtraction andthe tokens that were written to cache stay inside
nonCached. Downstream,SessionUsage.tokenscoerces the undefined write to
0viasafe(), and the same tokens are already sitting ininput.So
cache.write = 0isn't a dropped field. It's a misclassification, and it's silent because thearithmetic still balances.
The natural experiment
My database has both kinds of route side by side, which makes this easy to check. Average
inputperrequest, for every model with at least 250 requests that reported
cache.read > 0:inputcache.writeanthropic/claude-opus-5openai/astraopencode-go/gpt-5.6-lunaopenai/gpt-5.6-solopenai/gpt-5.6-terraopenai/gpt-5.6-lunaEvery route that reports a cache write has an average
inputof two to three tokens. Every route thatdoesn't carries thousands. That's the same quantity showing up in a different column, not two
different behaviours.
The obvious alternative — that these models simply miss cache more often, so the extra
inputisgenuinely uncached content rather than an unlabelled write — is ruled out by the filter. Every row in
these tables had
cache.read > 0, and you cannot read from a cache that nothing wrote. Caching isdemonstrably active on exactly the requests reporting zero writes. The only remaining reading is that
the writes happened and weren't labelled.
gpt-5.6-lunais the cleanest pair: onopencode-goit reports 81,910 average write and 3 averageinput; on
openaiit reports 0 write and 21,065 average input. Same model, same account.How much
Because it's a reclassification rather than a loss, this can be measured directly instead of
extrapolated. Summing
inputover requests that also hadcache.read > 0:inputrecordedcache.readinputopenai/gpt-5.6-lunaopenai/gpt-5.6-terraopenai/gpt-5.6-solopenai/astraanthropic/claude-opus-5That's 1.217 billion tokens on the
openaiprovider alone that would have been booked ascache.writeon a reporting route. The two reporting models round to 0.0%.The billing consequence follows from that: those tokens are priced at the input rate rather than the
cache-write rate, so the cost is understated by the write premium on 1.2B tokens rather than being
wrong by 100%. That is probably why the discrepancy showed up as a surprise on the invoice rather than
as an obviously broken number.
Same signature elsewhere
The pattern isn't OpenAI-specific — it's every route that omits the field:
inputcache.writexai/grok-4.6xai/grok-4.3opencode-go/minimax-m3opencode-go/glm-5.2xaiis the most absolute case: 5,892 assistant messages,max(cache.write)andsum(cache.write)both exactly zero, across two models. If a regression test is wanted, that's the cleanest input.
What I can't tell from here
Whether the
openairoute omitscache_write_tokensbecause the API doesn't send it, or because itarrives under a different key that the schema at
open-responses.ts:182-193doesn't look for. That's answerable without new instrumentation, though —
mapUsagealready stows theraw payload as
providerMetadata: { [providerMetadataKey]: usage }, so one capturedresponse.completedfrom agpt-5.6-*request would settle it.If the field genuinely isn't available on that route, the honest fix is probably not to invent it but
to stop the silent reclassification: either surface non-cached input as an explicit "uncached, may
include writes" quantity, or price it at the write rate when caching is known to be active. Reporting
0for a quantity that is demonstrably non-zero is the part that costs users money quietly.thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊