Skip to content

fix(opencode-plugin): strip trailing assistant messages to prevent Claude 4.x prefill error #17790

Closed

Description

@superdav42

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:

  1. Aborted/interrupted previous turn (sst/opencode#13768, PR #14772) — the aborted assistant has finish undefined, so the continuation gate at session/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.
  2. Continuation loop bug (sst/opencode#17982, PR #16921) — opencode 1.3.17 has a partial gate but it requires finish to be set.
  3. 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 exporting createPrefillGuardHook(deps). Strips trailing assistant messages from output.messages only when safe (preserves messages with finish === "tool-calls" or active pending/running/completed tool 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 — imports createPrefillGuardHook, composes a hook that runs messagesTransformHook first (so TTSR sees all assistants for rule scanning) then prefillGuardHook (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

  1. node --check .agents/plugins/opencode-aidevops/prefill-guard.mjs (syntax)
  2. node --check .agents/plugins/opencode-aidevops/index.mjs (syntax)
  3. Restart opencode-web.service and confirm clean plugin load in journalctl --user -u opencode-web.service
  4. 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


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

  1. github-actions commented on Apr 8, 2026

    @github-actions
    Contributor

    Thanks for the report, @superdav42.

    This issue has been queued for maintainer review. A maintainer will review it and either:

    • Approve it using: sudo aidevops approve issue 17790
    • Ask for more information if the report needs clarification
    • Close it if it's a duplicate or out of scope

    Tip: For faster, higher-quality issue reporting, use /log-issue-aidevops in your AI assistant session. It gathers diagnostics automatically and checks for duplicates.

  2. Repository owner locked as resolved and limited conversation to collaborators on Apr 8, 2026
  3. Repository owner unlocked this conversation on Apr 8, 2026
  4. Repository owner locked as resolved and limited conversation to collaborators on Apr 8, 2026
  5. Repository owner unlocked this conversation on Apr 8, 2026
  6. marcusquinn commented on Apr 8, 2026

    @marcusquinn
    Owner

    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.

  7. marcusquinn commented on Apr 8, 2026

    @marcusquinn
    Owner

    Maintainer Approval (cryptographically signed)

    APPROVE:issue:marcusquinn/aidevops:17790:2026-04-08T18:01:12Z
    
    -----BEGIN SSH SIGNATURE-----
    U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgeu2Ii1FVuPun+49v51FONnIskf
    TinwsexuL1MNgCwJ8AAAAQYWlkZXZvcHMtYXBwcm92ZQAAAAAAAAAGc2hhNTEyAAAAUwAA
    AAtzc2gtZWQyNTUxOQAAAEBiEJEYBhdXljTsTMRW3lSpF9CORE1Y4TBjepdhjvDI8FDxTe
    NwSYmogw6/OleaNYh1dQpafGdcOwi2O1v2ViwK
    -----END SSH SIGNATURE-----
    

    This approval was signed with a root-protected SSH key. It cannot be forged by automation.

    This issue is now locked. To propose scope changes, open a new issue referencing this one.

  8. added
    auto-dispatchEligible for automated worker dispatch
    and removed
    needs-maintainer-reviewExternal-author gate requiring maintainer approval before work begins
    on Apr 8, 2026
  9. Repository owner locked as resolved and limited conversation to collaborators on Apr 8, 2026
  10. self-assigned this
    on Apr 8, 2026
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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions