Repository navigation
Description
Problem
opencode webui (especially on mobile) intermittently returns:
This model does not support assistant message prefill. The conversation must end with a user message.
This rejects requests on Claude Opus/Sonnet 4.x (Anthropic native, GitHub Copilot, OpenRouter, Bedrock, Vertex — all providers that proxy these models).
Root cause
Anthropic rejects conversations that end with an assistant message for Claude 4.x. This state occurs in opencode in three documented ways:
- Aborted/interrupted previous turn (sst/opencode#13768, PR #14772) — the aborted assistant has
finishundefined, so the continuation gate atsession/prompt.ts(lastAssistant2?.finish && !["tool-calls"].includes(...)) fails open and the next iteration calls the LLM with a trailing assistant still present. Common on mobile where flaky connections cause aborts. - Continuation loop bug (sst/opencode#17982, PR #16921) — opencode 1.3.17 has a partial gate but it requires
finishto be set. - Max-steps hint (PR #14772) — when max steps is reached, opencode appends
{role: "assistant", content: max_steps_default}after the plugin hook runs.
Three upstream PRs (#14772, #16921, #18091) all add stripTrailingAssistant() in provider/transform.ts. None are merged as of opencode v1.3.17 / v1.4.0.
Fix
Apply the same defensive strip at the plugin layer via experimental.chat.messages.transform, which runs immediately before MessageV2.toModelMessages() in session/prompt.ts:837128.
Files
NEW: .agents/plugins/opencode-aidevops/prefill-guard.mjs— pure module exportingcreatePrefillGuardHook(deps). Strips trailing assistant messages fromoutput.messagesonly when safe (preserves messages withfinish === "tool-calls"or activepending/running/completedtool parts so legitimate tool-call flows are untouched). Wrapped in try/catch so a bug here can never break the hook chain.EDIT: .agents/plugins/opencode-aidevops/index.mjs— importscreatePrefillGuardHook, composes a hook that runsmessagesTransformHookfirst (so TTSR sees all assistants for rule scanning) thenprefillGuardHook(so anything TTSR didn't fix gets stripped).
Reference pattern
Composition mirrors the existing messagesTransformHook wiring at index.mjs:204. The guard module follows the same shape as ttsr.mjs (factory + closure pattern with qualityLog injection).
Safety
- Session DB is never modified — stripped messages still display in transcript history.
- Tool-call flows are preserved (only assistants with no live tool calls are stripped).
- Never throws — try/catch with diagnostic logging.
- Composition order: TTSR runs first (it scans the last 3 assistants for rule violations and may append a synthetic user correction); guard runs second so if TTSR didn't append a correction AND the conversation still ends with an assistant, we strip it.
Verification
node --check .agents/plugins/opencode-aidevops/prefill-guard.mjs(syntax)node --check .agents/plugins/opencode-aidevops/index.mjs(syntax)- Restart
opencode-web.serviceand confirm clean plugin load injournalctl --user -u opencode-web.service - Send a message via mobile webui that previously triggered the prefill error — should now succeed. Stripped messages produce a journal line:
[aidevops prefill-guard] stripped N trailing assistant message(s) ...
Acceptance criteria
- Mobile webui no longer returns prefill error on Claude Opus/Sonnet 4.x
- Tool-call flows on Claude 4.x continue working (not over-stripped)
- Plugin loads without errors after restart
- Journal shows guard activity when stripping occurs
- No regression in other providers (the guard only strips, never adds)
Related upstream
- This model does not support assistant message prefill / Github Copilot with Opus 4.6 anomalyco/opencode#13768 — original bug report
- Bug: OpenCode prompt loop continues after
finish=stop, triggering prefill error on claude-opus-4-6 anomalyco/opencode#17982 — continuation loop analysis - fix: disable assistant prefill for Claude 4.6 models anomalyco/opencode#14772 — fix PR (not merged)
- fix: keep Copilot Claude prompts user-final across continuation turns anomalyco/opencode#16921 — alternate fix PR (not merged)
- fix(opencode): CSP, cache headers, Anthropic prefill, idle timeout, and WebSocket URL credentials anomalyco/opencode#18091 — bundled fix PR (not merged)
aidevops.sh v3.6.162 plugin for OpenCode v1.3.17 with claude-opus-4-6 spent 1h 12m and 52,593 tokens on this with the user in an interactive session.
Activity
github-actions commentedon Apr 8, 2026
Thanks for the report, @superdav42.
This issue has been queued for maintainer review. A maintainer will review it and either:
sudo aidevops approve issue 17790Tip: For faster, higher-quality issue reporting, use
/log-issue-aidevopsin your AI assistant session. It gathers diagnostics automatically and checks for duplicates.fix(opencode-plugin): strip trailing assistant messages to prevent Cl…
marcusquinn commentedon Apr 8, 2026
This is a cosmetic upstream bug in OpenCode's message handling — not something we'll work around in the plugin layer. Typing "continue" typically clears it when encountered.
The upstream PRs (anomalyco/opencode#14772, #16921, #18091) target the correct fix location in
provider/transform.ts.aidevops.sh v3.6.165 plugin for OpenCode v1.3.17 with claude-opus-4-6 spent 18m and 4,633 tokens on this as a headless worker. Overall, 1h 15m since this issue was created.
fix: pre-start maintainer gate check in full-loop-helper.sh (GH#17810)
fix: pre-start maintainer gate check in full-loop-helper.sh (GH#17810) (
marcusquinn commentedon Apr 8, 2026
Maintainer Approval (cryptographically signed)
This approval was signed with a root-protected SSH key. It cannot be forged by automation.
fix(opencode-plugin): strip trailing assistant messages to prevent Cl…