Skip to content

Completed aborted assistant message with zero parts renders as blank/stuck in web UI #17895

@dzianisv

Description

@dzianisv

Problem

When an assistant turn is aborted before it emits any parts, OpenCode can persist a completed assistant message with MessageAbortedError and zero parts.

The backend already considers that turn finished, but the web UI has almost nothing to render for it, so the session can look blank or stuck even though it is no longer running.

This is a different shape from:

  • #17680 stale thinking state after interruption/restart
  • #15267 teardown race creating an empty continuation turn

This issue is specifically about the persisted message shape:

  • assistant message exists
  • time.completed is set
  • error.name = MessageAbortedError
  • parts.length = 0

Concrete evidence

Live session investigated:

  • session: ses_30ad0ece4ffegDiKt3ugw5wurA
  • final assistant message: msg_cf98d282b0019d4mnTo0zcQl7H

Persisted row state:

  • time.completed = 1773714305166 (2026-03-16 19:25:05 PDT)
  • error.name = MessageAbortedError
  • error.data.message = "The operation was aborted."
  • part_count = 0

Additional checks on that session:

  • orphan assistant rows with time.completed IS NULL: 0
  • pending/running tool parts: 0

So the backend state is not "still running". It is a completed aborted shell.

Why this is bad

The current UI path already treats MessageAbortedError specially, but when the assistant turn has zero visible parts, there is no substantive assistant content to show.

That makes the turn look blank or stuck, even though the backend has already finalized it.

Also, the persisted aborted error currently loses provenance. "The operation was aborted." does not tell us whether the source was:

  • server restart
  • user cancel
  • client disconnect
  • timeout
  • some other abort path

Proposed fix

1. Persist abort provenance

Extend MessageAbortedError payload to include an optional structured source, for example:

  • server_restart
  • user_cancel
  • client_disconnect
  • timeout
  • unknown

Populate it at the abort sites and in restart recovery.

2. Render zero-part aborted assistant turns explicitly in the web UI

If an assistant turn is:

  • completed
  • aborted (MessageAbortedError)
  • and has zero visible parts

then render a compact interruption card/banner from the error payload instead of effectively showing an empty assistant shell.

3. Add a narrow invariant log

Log only when an assistant message is finalized with:

  • error != null
  • time.completed != null
  • zero visible parts

This keeps diagnostics cheap and focused.

Acceptance criteria

  • A completed aborted assistant message with zero parts no longer appears blank/stuck in the web UI.
  • The UI shows a clear interrupted/aborted state for that turn.
  • MessageAbortedError can carry structured provenance for future diagnosis.
  • Tests cover the zero-part aborted case.

Metadata

Metadata

Assignees

Labels

webRelates to opencode on web / desktop

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions