Skip to content

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

Closed as not planned

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.

Activity

  1. dzianisv commented on Mar 17, 2026

    @dzianisv
    Author

    Fork and upstream work are now split cleanly:

    PR #17909 covers the persisted/rendered empty-aborted-turn visibility issue so these sessions stop looking blank/stuck in the UI. The fork PR keeps the runtime retry scoped to empty aborts only.

  2. removed
    webRelates to opencode on web / desktop
    on May 3, 2026
  3. github-actions commented on Jul 3, 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