Repository navigation
Description
Description
When the question tool ends up in a permanently running state (state.output never written, state.time.end never set, no follow-up assistant message), the session is marked busy forever. After that, every HTTP API call gated by SessionRunState.assertNotBusy() fails — including deleteMessage — so the user has no way to delete the offending message, abort the run, or otherwise escape the session via the documented HTTP API.
Concrete error returned by the server (paste from our log):
ERROR ... service=server error=Session ses_1d00d41caffe3C6jQ0xci17Keg is busy
at SessionRunState.assertNotBusy
at SessionRunState.assertNotBusy (definition)
at SessionHttpApi.deleteMessage
at SessionHttpApi.deleteMessage (definition)
The HTTP method deleteMessage checks assertNotBusy and throws, but the only thing keeping the session busy is a tool part that will never complete on its own. The session becomes a dead-end. The user's only recovery path today is to manually delete the session row or the stuck part row out of ~/.local/share/opencode/opencode.db.
This is essentially the same class of problem as the now-closed #14014 ("Revert fails when session is busy/generating"), but instead of revert the affected API is deleteMessage, and the trigger isn't an in-flight model generation — it's a question-tool part that has been abandoned mid-flight by a prior failure path.
How we got into the stuck-running state
We have three independent reproductions today from live ~/.local/share/opencode/opencode.db inspection. All three follow the same signature:
- A prior MCP tool call ended with
state.status: "error",error: "Not connected"(in our case the failing MCP server wascontext-mode). - A handful of normal
bash/readtool calls then ran successfully in the same session. - A subsequent
questiontool call fires. - The
questionpart is created withstate.status: "running"and updated once ~6 seconds later with what looks likestate.time.startwritten, butstate.outputis never written,state.time.endis never set, and no follow-up assistant message is created. The SSE/SDK side seems to never deliver the "this tool finished" event to the persisted part, so the part staysrunningindefinitely. - The session is now permanently busy from the run-state's point of view, and
deleteMessage(and presumably any otherassertNotBusy-guarded API) throws on every call.
We can produce a full forensic dump for any of the three sessions if useful (part ids, message ids, timing, etc.).
Expected Behavior
A deleteMessage request on a stuck session should either:
- Succeed by treating an indefinitely-
runningtool part as cancellable (since the user is explicitly trying to delete the message that owns it), or - Be allowed to force-abort the stuck run-state first (similar to how
session.undoalready aborts ongoing work before reverting, per #14014's expected-behavior section), or - At minimum offer a documented HTTP API to force-clear the busy flag / mark abandoned tool parts as errored, so the UI and external clients (we use CodeNomad as a wrapper) can recover without touching the SQLite DB by hand.
Plugins
context-mode@latest is the MCP plugin whose "Not connected" failure appears to be the upstream trigger in our reproductions, but the underlying lockout problem is in opencode itself: the run-state never garbage-collects an abandoned question tool part, and the HTTP API has no escape hatch when that happens. The same lockout would presumably occur for any tool that fails to deliver a terminal state event.
OpenCode version
v1.14.48 (reproduced today). Should still apply to v1.15.3 based on inspection of the assertNotBusy call sites — happy to re-verify on latest if asked.
Steps to reproduce
We have not yet pinned down a one-shot reproducer for getting the question part into the stuck-running state, but the secondary lockout is deterministic once you're there:
- Get any tool part stuck in
state.status: "running"with no terminal event (we can repro this via an MCP tool that errors with"Not connected"mid-session, followed shortly by aquestiontool call — happens reliably enough that we have three live cases today). - Confirm via the DB that
state.outputisundefinedandstate.time.endis missing for the offending part. - Call
DELETE /session/:sessionId/message/:messageId(or whichever routeSessionHttpApi.deleteMessageis bound to) on any message in the session. - Observe the server error:
Session <id> is busy at SessionRunState.assertNotBusy at SessionHttpApi.deleteMessage.
Result: the session is undeletable / unrecoverable through the documented HTTP API.
Screenshot and/or share link
N/A — server-side error. We can share part dumps if useful.
Operating System
Linux
Terminal
N/A (we hit this through CodeNomad, a third-party UI wrapper around opencode-server)
Activity
github-actions commentedon May 16, 2026
This issue might be a duplicate of existing issues. Please check:
runningstate with no recovery path)question.askedbut no documented way to answer via Server API (directly related to thequestiontool part behavior described here)If your issue is distinct from all of these, feel free to clarify the differences.
omercnet commentedon May 16, 2026
Thanks for the triage. Quick assessment of each candidate from my end:
#19023 — related root cause, not a duplicate. That issue and this one share the same underlying database state (
toolpart stuck instatus: "running"with no terminal event written). #19023's proposed fix is startup recovery: onopencode serveboot, scan for orphans, set them toerror, and emit Bus events. That would clean up sessions on the next server restart, but it does not address what I'm reporting: a server that is still running, has the orphan in its live DB right now, and rejectsdeleteMessage(and presumably every otherassertNotBusy-guarded API) for the duration of that server's life. We have three live orphaned-runningquestionparts on the sameopencode serveprocess from today and no server restart has occurred or is needed — the user just can't escape the session through the API.What this issue uniquely covers vs #19023:
deleteMessageshould not be permanently blocked by an orphanedrunningpart on the same message that the user is trying to delete (or at minimum there should be a documentedforce/ abort path).partrow, and the run-state machine reads it back as busy.DELETE /session/:id/message/:msgId, observeassertNotBusythrow" — independent of whatever produced the orphan in the first place.I think the right relationship is: #19023 is the root cause this is a symptom of, and a startup-recovery-only fix would be incomplete because it doesn't help long-running serves that hit this mid-flight. Happy to update the title/body of this issue to make that framing explicit if it'd help.
#27644 — related trigger surface, not a duplicate. That issue is about the missing documented HTTP answer flow for the
questiontool. If the answer path existed and was reliable, you'd never get into the stuck-runningstate from this code path in the first place, so it's adjacent. But this issue is specifically about the lockout once you're already there — independent of how the question got stuck. It would also help #27644 if there were a recovery path for sessions wedged by a missed answer.#27594 — different mechanism. That one is
tool_usewithouttool_resultcausing anAPIError, which is an Anthropic-payload-shape problem at the compaction boundary. Not the same as atoolpart being instatus: "running"with no terminal event. The user-visible outcome ("session permanently unrecoverable through the API") is the same, but the cause and the fix surface are unrelated.If maintainers prefer, I'm happy to:
deleteMessage/assertNotBusyescape-hatch gap, with Sessions permanently stuck after server restart or stream interruption — no startup recovery for orphaned messages/tool parts #19023 linked as the root-cause parent and OpenCode question tool emits question.asked but no documented way to answer via Server API #27644 as a related trigger surface, ordeleteMessagelockout reproduction) as a comment on Sessions permanently stuck after server restart or stream interruption — no startup recovery for orphaned messages/tool parts #19023 and a separate small issue strictly about the escape-hatch API. Either is fine — your call on how you'd like the bug-tracker shape to look.fix(chat): scope /question and /permission list+reply by directory
github-actions commentedon Jul 18, 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.