Skip to content

Queued user messages are serialized inconsistently across turns and break prompt caching #21518

Closed as not planned

Description

@CoolZxp

Description

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

  1. Start a session and send a prompt that keeps the model busy long enough to submit another message before the first run finishes.
  2. While that run is still in progress, send a second user message.
  3. Let OpenCode continue and consume that queued message.
  4. Send a third message after the queued message has already been processed.
  5. Inspect the model inputs for:
    • the request that first consumes the queued message
    • the later request that includes that same message as conversation history
  6. Notice that the queued message is wrapped with a <system-reminder> in the first request, but appears as original text in the later request.

Screenshot and/or share link

No response

Operating System

Windows 11

Terminal

Windows Terminal

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 8, 2026
  2. Sathvik-1007 commented on Apr 8, 2026

    @Sathvik-1007

    Bug: queued message cache miss across turns

    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.

  3. removed
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  4. github-actions commented on Jul 3, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  5. fluffyspace commented on Oct 13, 2026

    @fluffyspace

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions