Skip to content

Bug: deleteMessage (and other assertNotBusy-guarded APIs) permanently fail when a question tool part is stuck in state.status=running #27907

Closed as not planned

Description

@omercnet

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 was context-mode).
  • A handful of normal bash/read tool calls then ran successfully in the same session.
  • A subsequent question tool call fires.
  • The question part is created with state.status: "running" and updated once ~6 seconds later with what looks like state.time.start written, but state.output is never written, state.time.end is 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 stays running indefinitely.
  • The session is now permanently busy from the run-state's point of view, and deleteMessage (and presumably any other assertNotBusy-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:

  1. Succeed by treating an indefinitely-running tool part as cancellable (since the user is explicitly trying to delete the message that owns it), or
  2. Be allowed to force-abort the stuck run-state first (similar to how session.undo already aborts ongoing work before reverting, per #14014's expected-behavior section), or
  3. 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:

  1. 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 a question tool call — happens reliably enough that we have three live cases today).
  2. Confirm via the DB that state.output is undefined and state.time.end is missing for the offending part.
  3. Call DELETE /session/:sessionId/message/:messageId (or whichever route SessionHttpApi.deleteMessage is bound to) on any message in the session.
  4. 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

  1. github-actions commented on May 16, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is distinct from all of these, feel free to clarify the differences.

  2. omercnet commented on May 16, 2026

    @omercnet
    Author

    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 (tool part stuck in status: "running" with no terminal event written). #19023's proposed fix is startup recovery: on opencode serve boot, scan for orphans, set them to error, 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 rejects deleteMessage (and presumably every other assertNotBusy-guarded API) for the duration of that server's life. We have three live orphaned-running question parts on the same opencode serve process 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:

    • A runtime escape hatch is missing — deleteMessage should not be permanently blocked by an orphaned running part on the same message that the user is trying to delete (or at minimum there should be a documented force / abort path).
    • The wedge persists across client reconnects and UI restarts, not just across server restarts. Closing and reopening the CodeNomad client does not help because the orphan is in the persisted part row, and the run-state machine reads it back as busy.
    • I have a concrete deterministic step (4) for the lockout half — "call DELETE /session/:id/message/:msgId, observe assertNotBusy throw" — 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 question tool. If the answer path existed and was reliable, you'd never get into the stuck-running state 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_use without tool_result causing an APIError, which is an Anthropic-payload-shape problem at the compaction boundary. Not the same as a tool part being in status: "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:

    1. Update the title here to scope this issue narrowly to the deleteMessage/assertNotBusy escape-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, or
    2. Close this and add the forensic evidence (three live cases today, including the deleteMessage lockout 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.
  3. github-actions commented on Jul 18, 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.

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