Repository navigation
Closed as not planned
Closed as not planned
Copy link
Description
Summary
On OpenCode Go, the frank/DeepSeek-V4.1-Flash upstream repeatedly drops the prompt cache during active sessions, forcing full-context re-reads that are billed at the full uncached input price. Across a ~17h window on one workspace, roughly 50% of the billed usage was unnecessary re-reads caused by this.
Evidence (console request-log export, 2026-09-20)
447 requests, total billed cost $1.76:
| upstream | requests | cache hit | full-context re-reads | cost |
|---|---|---|---|---|
frank/DeepSeek-V4.1-Flash |
260 | 94.2% | 14 | $1.16 |
deepseek/deepseek-v4.1-flash |
108 | 97.2% | 1 | $0.27 |
deepseek-flash |
75 | 95.9% | 2 (idle TTL) | $0.33 |
- 18 full-context re-reads totaling 5.27M tokens (≈ $0.77 at $0.15/M uncached vs $0.003/M cached)
- 58 partial cache regressions (cacheRead drops mid-session, e.g. 542,848 → 522,240) totaling ~1.55M tokens (≈ $0.23)
- ≈ $1.00 of $1.76 (≈57%) of billed usage was cache-loss waste
Key pattern on frank (session ses_f3f29f46fffe5Tje7gZiWLN323):
16:16:21 frank cacheRead=451,328 (normal hit)
16:17:01 frank input=452,421 cacheRead=384 $0.068 ← full re-read 40s later
16:17:15 frank cacheRead=452,480 (recovers)
16:24:49 frank input=488,835 cacheRead=384 $0.075 ← again
16:25:36 frank input=491,442 cacheRead=384 $0.074 ← again, 47s later
- Re-reads happen seconds/minutes after a normal hit — not idle TTL
- Same client, same day: the other two upstreams had 0 mid-session losses
- Two
429failovers fromdeepseek-v4p1-flashpushed traffic ontofrank - Sessions are pinned per upstream (each session stays on one route), so this is not client-side routing churn
Still happening after prompt_cache_key
We enabled compatibility.supportsPromptCacheKey and restarted the app at 17:18. Local session data shows the losses continue on the same session:
17:22:06 in=293,338 cacheRead=384 $0.0441
17:26:54 in=295,313 cacheRead=384 $0.0446
17:35:05 in=301,422 cacheRead=384 $0.0455
17:43:05 in=314,084 cacheRead=384 $0.0475
17:44:14 in=318,302 cacheRead=384 $0.0479
…while other sessions on the same client had 0 losses (98.9% / 99.1% hit). So the client-supplied prompt cache key does not appear to provide sticky routing / cache affinity for frank.
Impact
- The advertised 6x value multiplier is effectively cut to ~2.6x for affected sessions.
- Quota is consumed much faster than expected; single re-reads cost up to $0.12 as context grows (worst observed: 810K tokens, $0.1225).
- Users are billed for tokens that were already processed and should have been cache hits.
Request
- Please treat this as urgent and investigate the
frankupstream cache behavior (replica pool without shared cache? aggressive eviction? routing without affinity?). - We ask that the wasted usage on this account be credited / quota restored while this is being fixed.
- Please confirm whether the gateway honors client
prompt_cache_keyfor sticky routing. - Happy to provide the full request-log CSV export or coordinate a repro.
Environment
- OpenCode Desktop 2.0.11 (macOS, Apple Silicon), OpenCode Go
- Org:
org_01KYVMGQHGPCRQFCCFGT3F4QEP - Affected session:
ses_f3f29f46fffe5Tje7gZiWLN323(2026-09-20 15:00–17:45)
Reacted by geminigeek and Robin
Metadata
Metadata
Assignees
Labels
No labels
Activity
Git-yzc commentedon Sep 21, 2026
Independent corroboration: cache loss reproduces with raw HTTP, on a different client and OS
Confirming this from a different client (DeepSeek Harness 2.0.13, Windows 11) against the same endpoint
https://opencode.ai/zen/go/v1/chat/completions, modeldeepseek-v4.1-flash. The losses are not client-side prompt construction, and they reproduce without any agent involved.1. Byte-identical requests flap between full hit and full miss
Same payload, same headers, same
x-opencode-session, sent sequentially:Nothing in the request differs between those calls, so no client-side cache key, header, or prompt handling can account for it. A second run 20 minutes later gave 1/12 — the rate varies with time, which points at upstream replica/eviction behavior rather than a deterministic bug.
2. Session id does influence locality, but does not provide stickiness
Alternating two
x-opencode-sessionvalues across 12 identical requests gave 3/12 cold misses, versus 1/12 when repeating a single id. So the header is being used for something, but it is clearly not enough to keep a prefix warm by itself.3. Regression timeline from client session records
From 8,150 assistant turns of local session data (per-step
inputTokens/cacheReadTokens; "mid-session" excludes the first two steps of a session, which are always cold):Median per-step latency moved with it: 8.0s (09-16) → 13.8s (09-20) → 13.5s (09-21).
The prompt prefix itself is stable — most steps add only 43–136 uncached tokens while the prompt grows monotonically:
So the server is dropping a warm prefix, not the client rewriting history. Note the shape is flapping, not a one-way break — which is what per-upstream cache pools without affinity would produce.
4. Other models on the same endpoint (6 identical requests each, same run)
deepseek-v4.1-flashdeepseek-v4-flashglm-5.3-flashminimax-m3Consistent with your
frankfinding that this is upstream-specific rather than model-wide.5. Client note, in case it affects affinity routing
This client currently sends no per-conversation session id on the adapter path used here (DeepSeek Harness is listed under "Known Problematic Clients" on the Go docs). It is worked around by configuring a fixed
x-opencode-sessionvalue for all conversations, so every conversation shares one routing key. Flagging it since it is the only session signal the gateway receives from us — though given (1), it cannot be the whole story.Happy to share the repro script or the raw session records.
Veld101 commentedon Sep 21, 2026
I looked into the caching code and here is what I found:
Code Analysis
V1 path (
packages/opencode/src/provider/transform.ts:1310-1323)The
promptCacheKeyis only set for specific SDK packages:@ai-sdk/deepinfra/@ai-sdk/cerebras→prompt_cache_key(snake_case)@ai-sdk/openai/@ai-sdk/azure/@ai-sdk/xai/@ai-sdk/mistral/venice-ai-sdk-provider→promptCacheKey(camelCase)DeepSeek uses
@ai-sdk/openai-compatible, which is NOT in the list. So the V1 path does not send any cache key to DeepSeek by default.V2 path (
packages/core/src/session/runner/llm.ts:204-214)V2 sets
promptCacheKeyunder theopenaiprovider options namespace, which may not be forwarded to DeepSeek-compatible endpoints.DeepSeek Cache Mechanism
DeepSeek prompt cache is content-prefix-based, not key-based:
So
prompt_cache_keyis actually irrelevant for DeepSeek — the cache is content-addressed.The
frankNode ProblemComparing the three upstreams from the reporter data:
frank/DeepSeek-V4.1-Flashdeepseek/deepseek-v4.1-flashdeepseek-flashThe
franknode has significantly worse cache performance. This strongly suggestsfrankis a replica pool without shared cache, and requests are being routed to different replicas without cache affinity.The mid-session cache drops (seconds/minutes after a normal hit, not idle TTL) are consistent with a load balancer rotating requests across replicas that each maintain independent caches.
Conclusion
This is a gateway routing / infrastructure issue, not a client-side code bug. The fix needs to happen at the
frankupstream level:The client-side
prompt_cache_keycannot solve this because DeepSeek cache is content-addressed, not key-addressed.Veld101 commentedon Sep 23, 2026
Re-checked the current
v2code and two parts of my analysis above need correcting.1. The V2 request shape has moved and is not what I described.
It is no longer
providerOptions.openai.promptCacheKeyinrunner/llm.ts. Onv2today:packages/core/src/session/model-request.ts:237-252sets a first-classpromptCacheKeyfromaffinity = session.parentID ?? session.fork?.sessionID ?? session.id, and always sends header affinity on every request:x-session-affinity,X-Session-Id,x-parent-session-id,x-opencode-session.packages/ai/src/protocols/openai-chat.ts:763-779only adds the body fieldprompt_cache_keywhenmodel.compatibility.supportsPromptCacheKey === true(default off). The comment there notes header-based affinity is unaffected by that gate.packages/core/src/plugin/provider/prompt-cache-key.tsflips that flag on for a whitelist of providers. The OpenCode Go route is not in it, so its body key stays off unless a user opts in.So the client already sends session-header affinity unconditionally. Enabling
supportsPromptCacheKey(as the report did at 17:18) adds the body key on top — and the losses continued. That is consistent with the gateway not using either signal for sticky routing, rather than the client omitting a key.2. The V1 note still holds. The
providerID.startsWith("opencode")branch intransform.tsis nested inside theapi.id.includes("gpt-5")block, so DeepSeek never reaches it, and the package list I quoted is accurate.On the question above — detect the drop and fail or back off instead of paying for the re-read:
There is no client-side action that avoids the charge.
cacheReadin the usage after the fact.The conclusion is unchanged: this has to be fixed upstream (cache affinity across replicas, or a shared cache layer), not in the client.
geminigeek commentedon Sep 23, 2026
this need to be fixed with priority , just resumed my GO after 2 month , this issue is crazy
geminigeek commentedon Sep 23, 2026
using legacy api key always route to direct deepseek endpoint , this is the workaround i found
zhengkaics commentedon Sep 26, 2026
Update 09-26: cache losses now measurably burning the Go $60 Flash credit at full input price
36-hour measurement from my own account. All 8 rows are the same session, prefix growing monotonically (586k → 595k tokens), model
opencode-go/deepseek-v4.1-flash.cachedfrom localsession_messageusage,billedfrom the console:Two of the four misses are explicable idle cold starts. The other two are the reported pathology: a full ~590k re-read 43 seconds and 31.5 minutes after requests that had just returned ~100% hits on the same session, with a strictly growing prefix. Billing matches the full input rate exactly (590k x $0.15/M ~= $0.0885); the catalog still advertises cache read at $0.003/M, unchanged.
Token hit rate on my traffic: 99.07% on 09-21 (274 requests) -> 49.94% on 09-25 -> 49.97% on 09-26.
Why this matters beyond one account: the Go plan advertises $10/month -> ~$60 of DeepSeek Flash usage (~6x). That arithmetic only works with high cache-hit rates — OpenCode's own request estimate assumes ~68k cache-read tokens vs ~790 fresh input per request. When cache-read collapses to ~256 on turn starts, each 590k-context turn costs ~$0.089 instead of ~$0.002 (~45x), so the $60 credit buys roughly an order of magnitude less. In the window above, $0.37 was billed and ~$0.35 of that was pure cache-failure waste.
This lines up with the two findings already in this thread: the byte-identical raw-HTTP flap (upstream replica/eviction behavior, not client-side), and the source analysis (client sends session affinity headers unconditionally; the gateway does not use them for sticky routing). Nothing client-side can recover credit that has already been billed as full-context input. Fix has to be gateway-side cache affinity (or a shared cache layer).
neriousy commentedon Oct 21, 2026
Thanks for the thorough analysis. This was most likely fixed since it was reported: Go routing and caching for DeepSeek have changed a lot. Closing because there's been no activity for a while. If you run into full-context re-reads again, please open a new issue with the time, session ID and request IDs from the console logs. If it used up your allowance, email help@anoma.ly with your workspace ID.