anomalyco / anomalyco/opencode
question tool: aborting mid-question leaves the request pending forever and never publishes question.rejected
@nexxeln is already working on this.
Since Aug 29, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
Aborting a turn while the question tool is waiting for user input kills the turn correctly (message finalized as MessageAbortedError, tool part marked interrupted: true, session idle) — but the pending question is never rejected:
- it stays in the registry:
GET /questionstill reports it for a dead turn - no
question.rejected/question.repliedevent is ever published POST /question/:id/rejecton it returns200even though the turn is gone
The Desktop app renders the question dock instead of the composer whenever a question exists in its store, and only clears that store on question.replied/question.rejected. Since neither is ever published on abort, the composer stays blocked and Stop is a no-op — the session can't be continued, only an app restart fixes it.
Root cause: Question.ask (packages/opencode/src/question/index.ts) waits with Effect.ensuring(Deferred.await(deferred), pending.delete) — on interrupt the entry is silently dropped/leaked and no event is published. Same shape in Permission.ask. SessionProcessor.cleanup() only marks the tool part errored. All unchanged on latest dev.
Related: #35840 (same family — note that on 1.18.25 the entry leaks rather than being cleaned up, so answering no longer 404s, but the stale-UI outcome is the same), #30066, #15172, #46135, #43376.
Fix suggestion: on turn abort, reject pending asks through the normal path (fail the deferred + publish question.rejected / permission.replied) instead of the silent finalizer. Client-side defense in depth: clear a pending question when its tool part becomes error/interrupted.
Plugins
none
OpenCode version
1.18.25 (also checked: packages/opencode/src/question/index.ts has no changes on latest dev)
Steps to reproduce
Full runnable harness (mock provider + driver + config): https://gist.github.com/henrychu04/ad874b73679b0318a81d4a3ae9db2cf9
The mock is an OpenAI-compatible server whose first completion is a single question tool call, so no real LLM is needed. It ships a mock/mock-model via opencode.json (npm: @ai-sdk/openai-compatible, baseURL: http://127.0.0.1:41123/v1).
# terminal 1
node mock-provider.js # mock provider on 127.0.0.1:41123
# terminal 2 (isolated data/config so this doesn't touch your install)
git init project
XDG_DATA_HOME="$PWD/data" XDG_CONFIG_HOME="$PWD/config" \
opencode serve --port 41234 --hostname 127.0.0.1
# terminal 3
REPRO_PROJECT_DIR="$PWD/project" node drive.js
drive.js connects an SSE listener to GET /event, creates a session, sends a prompt (the mock model calls question), waits for question.asked, then POST /session/:id/abort, and prints the resulting timeline:
[+3.280s] SSE question.asked ...
[+4.284s] >>> POST /session/:id/abort
[+4.571s] SSE session.status {"status":{"type":"idle"}}
[+4.910s] PROMPT REQUEST RESOLVED: HTTP 200
[+4.911s] SSE message.part.updated part=tool tool=question status=error err="Tool execution aborted"
[+4.911s] SSE message.updated msg error="MessageAbortedError"
[+6.953s] GET /session/status -> {} (server idle and healthy)
[+6.973s] GET /question -> [ ...aborted question, still pending... ]
[+7.376s] follow-up prompt admitted (HTTP 200)
wire tally: question.asked=1, question.rejected=0, question.replied=0
Finally, POST /question/<que_id>/reject on the ghost returns 200 true.
Operating System
macOS
Terminal
n/a — opencode serve + Desktop app
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.