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
{{ message }}
Repository navigation
Queued user messages are serialized inconsistently across turns and break prompt caching #21518
When a user sends a follow-up message while the assistant is still running, OpenCode wraps that queued message in a temporary block before sending the next request to the model.
That wrapper is only applied for that in-flight turn. It is not preserved as part of the message’s stable history. On later turns, the same message is sent back to the model in its original unwrapped form.
As a result, the exact same historical user message is serialized differently across requests:
when first consumed from the queue: wrapped in <system-reminder>
on later turns: original text
This breaks prompt caching, since the prompt prefix is no longer stable across turns even though the underlying conversation history has not meaningfully changed.
The original user-visible message text should remain unchanged in the UI, but the model-facing representation of queued messages should be stable across future turns.
Plugins
None
OpenCode version
1.4.0
Steps to reproduce
Start a session and send a prompt that keeps the model busy long enough to submit another message before the first run finishes.
While that run is still in progress, send a second user message.
Let OpenCode continue and consume that queued message.
Send a third message after the queued message has already been processed.
Inspect the model inputs for:
the request that first consumes the queued message
the later request that includes that same message as conversation history
Notice that the queued message is wrapped with a <system-reminder> in the first request, but appears as original text in the later request.
Root cause found.prompt.ts:1483-1499 wraps queued user message text parts in <system-reminder> tags via direct mutation (p.text = [...]). Mutation is in-memory only — never persisted to DB.
What happen:
Each loop iteration fetches fresh msgs from DB (filterCompactedEffect → stream → page → hydrate → JSON.parse). Fresh objects every time.
Lines 1483-1499 mutate p.text with wrapper — works for current step LLM call.
Next iteration: fresh DB fetch → original unwrapped text → wrap again from scratch.
Next turn: step resets to 0. step > 1 = false on first step. Same message now unwrapped. Provider sees different content → cache miss.
Data flow: hydrate() (message-v2.ts:550-574) does shallow spread of row.data (JSON parsed) — no shared refs. step is let step = 0 per runLoop call (line 1343). Wrapping only fires on continuation steps (after tool calls), not initial response.
No double-wrap risk — fresh text each fetch. But same message serialized differently across turns = cache key mismatch.
PR coming.
Reacted by Slepkaviba, Martin Dengler, Pavel Kocka and ScaniX
Heads-up: this still reproduces on v2.0.28 (official @opencode/cli, long-running server), and the same "queued message is re-processed on every loop step" trigger now has a visible consequence beyond cache misses — the duplicated message content is actually re-injected into the model prompt once per step, so the model sees the user's message 4–7 times in a row. Opened with fresh evidence: #55204. Since this issue was closed not_planned, linking it here so the two stay connected.
Activity
Sathvik-1007 commentedon Apr 8, 2026
Bug: queued message cache miss across turns
Root cause found.
prompt.ts:1483-1499wraps queued user message text parts in<system-reminder>tags via direct mutation (p.text = [...]). Mutation is in-memory only — never persisted to DB.What happen:
filterCompactedEffect→stream→page→hydrate→ JSON.parse). Fresh objects every time.p.textwith wrapper — works for current step LLM call.stepresets to 0.step > 1= false on first step. Same message now unwrapped. Provider sees different content → cache miss.Data flow:
hydrate()(message-v2.ts:550-574) does shallow spread ofrow.data(JSON parsed) — no shared refs.stepislet step = 0perrunLoopcall (line 1343). Wrapping only fires on continuation steps (after tool calls), not initial response.No double-wrap risk — fresh text each fetch. But same message serialized differently across turns = cache key mismatch.
PR coming.
github-actions commentedon Jul 3, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
fluffyspace commentedon Oct 13, 2026
Heads-up: this still reproduces on v2.0.28 (official
@opencode/cli, long-running server), and the same "queued message is re-processed on every loop step" trigger now has a visible consequence beyond cache misses — the duplicated message content is actually re-injected into the model prompt once per step, so the model sees the user's message 4–7 times in a row. Opened with fresh evidence: #55204. Since this issue was closednot_planned, linking it here so the two stay connected.