anomalyco / anomalyco/opencode

Queued messages are silently dropped when a turn is interrupted

Open
#40,955 2 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Aug 7, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

opencode version

1.18.14 (bug held at dev: packages/opencode/src/session/prompt.ts, SessionPrompt.cancel)

Client

TUI (Esc interrupt) — equally reproducible headless via POST /session/:id/abort.

What happens

  1. Start a turn that runs for a while (a long tool call is the easy case).
  2. While it runs, send one or more follow-up messages — the TUI shows them with the QUEUED tag.
  3. Interrupt the turn (Esc / abort endpoint).

Expected: the queued messages are still processed once the turn settles — they were explicitly queued for a turn, and interrupting should mean "stop the current work", not "silently discard my pending input".

Actual: the QUEUED tag vanishes, the turn aborts, and the queued messages are never processed. They are persisted in history as user messages (written at send time) but no turn ever answers them — they're silently stranded. From the user's point of view the queue just evaporated.

Evidence

Live-session forensics on sqlite storage: the queued questions exist as user rows in message/part long before the abort (e.g. time_created 18 min earlier); the abort is logged (message=cancel ... error=Aborted); afterwards the session sits permanently idle with unanswered user messages at the tail. Minimal headless reproducer: create session → prompt A (a turn with a long tool call) → prompt B while busy → POST /session/:id/abort → session idles; B is in history, never answered.

Root cause (dev branch)

SessionPrompt.cancel stops the current run via SessionRunState.cancel and returns. The run loop is history-driven: queued input is simply a user message behind the in-flight turn, normally picked up by a subsequent loop iteration (steer semantics). After a cancel nothing restarts the loop, so any user message behind the last assistant message is stranded forever.

Proposed fix

After the cancellation settles, if there are user messages behind the last assistant message (queued input that was never picked up), start a fresh loop for them. Bare interrupts keep today's semantics: with nothing queued behind the aborted turn, the interrupted question is not re-answered. PR with regression tests for both directions incoming.

Until a release ships, the behavior can be restored with a user-level plugin that watches for the abort→idle sequence and re-prompts when unanswered user messages tail the session.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.