You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Aborted assistant turn persisted with zero content bricks the session: empty assistant message replayed to provider (400 "must not be empty") #37946
A turn was aborted mid-stream (the TUI had frozen after an OS suspend/resume, so I aborted). OpenCode persisted the aborted assistant message with error: MessageAbortedError and only step-start / reasoning / patch parts — no text, no tool call.
From then on, every subsequent prompt replays this message in the history as an assistant message with empty content. Moonshot (via OpenRouter) rejects the whole request:
[Moonshot AI] Invalid request: the message at position 29 with role 'assistant' must not be empty (400)
It snowballs: each failed retry persists another empty assistant message with error: APIError, so the failing position advances (29 → 30 → …) and the session is permanently unusable. I had to manually delete the artifact messages from opencode.db to recover the session.
Expected behavior: an aborted/errored assistant turn with no serializable content should either not be persisted, or be filtered out when building the model messages (the assistant-side equivalent of the guard requested in #31046 for empty text parts).
Steps to reproduce
Session with a strict provider (openrouter / moonshotai/kimi-k3)
Abort a turn while the model is still in reasoning, before any text or tool part is emitted
Send any new message → 400 "must not be empty"; each retry fails the same way and appends a new empty artifact message
I reproduced the strict-provider replay failure described here and opened #38270 with a targeted fix.
The patch post-processes the final ModelMessage list for @ai-sdk/openai-compatible: if an assistant wire message has no non-empty text, it prepends a single-space text part. This covers the aborted step-start / reasoning-only shape from this issue, empty-text-only messages, tool-call-only turns, and per-step pieces created after step-start splitting without dropping tool-call or reasoning history.
Validation includes 7 regression cases (43/43 tests passing), clean typecheck, and a replay of a previously bricked Kimi K3 session where the follow-up request now succeeds.
This is intentionally broader than #33899: that PR only handles messages with tool calls and therefore leaves this issue's no-tool-call aborted reasoning-only shape unchanged.
Reproduced on opencode 1.18.19 (Windows, provider opencode-go, model kimi-k3), so this is still unfixed in the latest 1.x.
Trigger in our case: an upstream connectivity failure mid-stream, not a manual abort:
08:00:38 user asks a question, streaming starts (step 0)
08:02:21 stream error: "Cannot connect to API" -> stream dies mid-generation
08:02:25 opencode retries the stream
08:03:10 step 1 starts, new assistant message msg_03d182f4 created, request sent
08:03:17 upstream rejects the whole payload: 400 "the message at position 26 with
role 'assistant' must not be empty" -> msg_03d182f4 stays persisted with
ZERO parts
08:04:28 next user turn replays the poisoned history -> same 400 -> yet another
empty assistant row is persisted
Amplifying effect worth noting: every failed retry adds another empty assistant message to the session, so the poison history grows and the session is permanently bricked — there is no way out from the UI.
Storage detail (1.x SQLite): the empty rows are assistant rows in the message table of ~/.local/share/opencode/opencode.db with zero matching rows in part.
Workaround (fixed both affected sessions for us): back up the DB, then delete the partless assistant messages:
DELETEFROM part WHERE message_id IN (SELECTm.idFROM message m
LEFT JOIN part p ONp.message_id=m.idWHERE json_extract(m.data, '$.role') ='assistant'GROUP BYm.idHAVINGCOUNT(p.id) =0);
The proposed fix in #38270 (sanitize the outgoing payload) would mask the symptom, but it would not stop empty assistant rows from accumulating in the DB. Ideally opencode should also avoid persisting the assistant scaffold when the stream fails before any content arrives, or clean it up on retry.
Still reproduces on 1.18.23 (latest release) — I just hit the snowball variant: a rate-limited turn persisted a zero-part assistant message with error: APIError, and every subsequent request failed with the message at position 772 with role 'assistant' must not be empty, each retry appending yet another empty artifact message (391 → had to delete 3 rows from opencode.db to recover).
The guards currently on dev (skip zero-part messages, skip errored messages, skip step-start-only messages) cover the zero-part case, but three shapes still replay as empty assistant content:
Aborted turn with a patch part — the abort carve-out treats any non-step-start/reasoning part as content, so [step-start, reasoning, patch] survives; patch never serializes, leaving a reasoning-only message that OpenAI-compatible converters turn into content: "". This is the exact shape in this issue's report.
Reasoning-only turn with no error field (crash/disconnect before the error is persisted) — the error filter never runs.
Turn with only empty text parts (text: "").
Verified against dev by calling MessageV2.toModelMessages directly with these shapes — all three still produce empty assistant messages.
The fix tightens the abort carve-out to count only model-visible parts (non-empty text or tool) and drops any assistant turn that produces no model-visible content at conversion time — so it also heals already-bricked sessions without a DB migration.
Same thing bricked several of my sessions (Kimi K3 behind a litellm gateway). Until the core fix in #45839 lands, you can work around it with a small plugin that hooks experimental.chat.messages.transform and sanitizes replayed history before conversion — it drops empty text parts and step segments that contain only reasoning (which OpenAI-compatible providers drop, producing the empty assistant message): https://github.com/impptg/opencode-fix-empty-assistant-messages
For an already-bricked session: the stored history in ~/.local/share/opencode/opencode.db needs a cleanup (delete empty text parts / reasoning-only step segments of the offending assistant messages), then restart the client. Once #45839 is merged the plugin can be removed — conversion-time filtering also heals bricked sessions automatically.
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.
Activity
github-actions commentedon Jul 20, 2026
This issue might be a duplicate of or closely related to existing issues not already mentioned. Please check:
fix(provider): inject placeholder text for textless tool-call assista…
codeg-dev commentedon Jul 22, 2026
I reproduced the strict-provider replay failure described here and opened #38270 with a targeted fix.
The patch post-processes the final
ModelMessagelist for@ai-sdk/openai-compatible: if an assistant wire message has no non-empty text, it prepends a single-space text part. This covers the abortedstep-start/ reasoning-only shape from this issue, empty-text-only messages, tool-call-only turns, and per-step pieces created afterstep-startsplitting without dropping tool-call or reasoning history.Validation includes 7 regression cases (43/43 tests passing), clean typecheck, and a replay of a previously bricked Kimi K3 session where the follow-up request now succeeds.
This is intentionally broader than #33899: that PR only handles messages with tool calls and therefore leaves this issue's no-tool-call aborted reasoning-only shape unchanged.
fix(provider): inject placeholder text for empty assistant wire messa…
saltyfisher commentedon Aug 26, 2026
Reproduced on opencode 1.18.19 (Windows, provider
opencode-go, modelkimi-k3), so this is still unfixed in the latest 1.x.Trigger in our case: an upstream connectivity failure mid-stream, not a manual abort:
Amplifying effect worth noting: every failed retry adds another empty assistant message to the session, so the poison history grows and the session is permanently bricked — there is no way out from the UI.
Storage detail (1.x SQLite): the empty rows are
assistantrows in themessagetable of~/.local/share/opencode/opencode.dbwith zero matching rows inpart.Workaround (fixed both affected sessions for us): back up the DB, then delete the partless assistant messages:
The proposed fix in #38270 (sanitize the outgoing payload) would mask the symptom, but it would not stop empty assistant rows from accumulating in the DB. Ideally opencode should also avoid persisting the assistant scaffold when the stream fails before any content arrives, or clean it up on retry.
impptg commentedon Aug 28, 2026
Still reproduces on 1.18.23 (latest release) — I just hit the snowball variant: a rate-limited turn persisted a zero-part assistant message with
error: APIError, and every subsequent request failed withthe message at position 772 with role 'assistant' must not be empty, each retry appending yet another empty artifact message (391 → had to delete 3 rows fromopencode.dbto recover).The guards currently on
dev(skip zero-part messages, skip errored messages, skip step-start-only messages) cover the zero-part case, but three shapes still replay as empty assistant content:patchpart — the abort carve-out treats any non-step-start/reasoningpart as content, so[step-start, reasoning, patch]survives;patchnever serializes, leaving a reasoning-only message that OpenAI-compatible converters turn intocontent: "". This is the exact shape in this issue's report.errorfield (crash/disconnect before the error is persisted) — the error filter never runs.text: "").Verified against
devby callingMessageV2.toModelMessagesdirectly with these shapes — all three still produce empty assistant messages.PR with a fix + regression tests: #45839
The fix tightens the abort carve-out to count only model-visible parts (non-empty text or tool) and drops any assistant turn that produces no model-visible content at conversion time — so it also heals already-bricked sessions without a DB migration.
impptg commentedon Aug 31, 2026
Same thing bricked several of my sessions (Kimi K3 behind a litellm gateway). Until the core fix in #45839 lands, you can work around it with a small plugin that hooks
experimental.chat.messages.transformand sanitizes replayed history before conversion — it drops emptytextparts and step segments that contain only reasoning (which OpenAI-compatible providers drop, producing the empty assistant message): https://github.com/impptg/opencode-fix-empty-assistant-messagesFor an already-bricked session: the stored history in
~/.local/share/opencode/opencode.dbneeds a cleanup (delete empty text parts / reasoning-only step segments of the offending assistant messages), then restart the client. Once #45839 is merged the plugin can be removed — conversion-time filtering also heals bricked sessions automatically.thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊