Repository navigation
Trailing-assistant 400 on llama.cpp/vLLM with thinking-on templates (Qwen3, DeepSeek-R1, GLM-thinking, Kimi-K2-Thinking, MiniMax-M2) #27920
Description
Symptom
Local OpenAI-compatible servers running thinking-on-by-default chat templates (llama.cpp --reasoning on, vLLM with reasoning, TGI with thinking, mistral.rs, etc.) reject any opencode request whose last message is role:"assistant", with:
HTTP 400 {"error":{"message":"Assistant response prefill is incompatible with enable_thinking."}}
opencode emits a trailing-assistant message in two situations, both of which trip this error:
- Empty trailing assistant —
message-v2.toModelMessagesEffectsometimes builds an assistantUIMessagewhose only parts are[step-start, reasoning("")].convertToModelMessagescollapses that tocontent:"", which is sent as a trailing assistant turn. - Non-empty trailing assistant —
session/prompt.tsdeliberately injects aMAX_STEPSwrap-up instruction asrole:"assistant"(response continuation / prefill).
Reproduction
- Run a llama-server with a thinking template, e.g.:
llama-server --model Qwen3.5-9B-...gguf --reasoning on --jinja --port 8080 - Point opencode at it via an
@ai-sdk/openai-compatibleprovider inopencode.json. - Run any agent with a
stepslimit small enough to trigger MAX_STEPS, or any flow that emits an empty-reasoning assistant turn. - Observe HTTP 400s in the llama-server log.
Affected model families
Every 2025-2026 open-weight thinking family using enable_thinking-branching templates:
- Qwen3 hybrid (all sizes), Qwen3-Thinking-2507, Qwen3-VL, Qwen3.5, Qwen3.6, QwQ-32B
- DeepSeek-R1, R1-0528, V4 (when thinking on)
- GLM-4.6, GLM-4.7 thinking
- Kimi-K2-Thinking
- MiniMax-M2
Not affected: Qwen2.5, Qwen3-Coder, Qwen3-Instruct-2507, all Anthropic/OpenAI/Google/Bedrock-Anthropic models (these either don't use enable_thinking branching or accept prefill natively).
Upstream / cross-framework references
- Misc. bug: Qwen3.5 with Opencode: Assistant response prefill is incompatible with enable_thinking. ggml-org/llama.cpp#20861 — exact symptom (opencode + Qwen3.5), still open
- server: allow assistant prefill when the prefill itself closes the <think> block ggml-org/llama.cpp#21889 — proposal to relax the check (open, no PR)
- Skills cause "Assistant response prefill is incompatible with enable_thinking" with Qwen 3.5 mastra-ai/mastra#15234 — same symptom in a different framework
Why fix it in opencode
A llama.cpp-side fix is unlikely soon and would only cover llama.cpp. opencode is the boundary where the per-provider request shape is decided, and where capability data already lives. Fixing it here also covers vLLM/TGI/mistral.rs, which have analogous behaviour but no shared upstream change to wait for.
Proposed fix (three PRs)
- Empty-trailing case — extend the existing
transform.tsempty-content filter (currently anthropic + bedrock only) to@ai-sdk/openai-compatible. Refactors two near-identical map+filter chains into one helper. Model.prefillcapability — addprefill: optional BooleanonModeland on the user-facing config schema. No consumer wiring yet.- Consumer + runtime probe —
ProviderTransform.canAcceptTrailingAssistant(model)with three-layer precedence (explicit / auto-inference / default true).session/prompt.tsMAX_STEPS routes betweenrole:assistantandrole:userbased on it. Runtime probe of<baseURL>/props(llama.cpp) detectsenable_thinking-branching templates automatically — no user config needed for the common case.
Thinking stays enabled in the request body throughout — only the role of the synthetic MAX_STEPS message changes from assistant to user. The model thinks and writes its wrap-up normally.
Activity
github-actions commentedon May 16, 2026
This issue might be a duplicate of existing issues. Please check:
role=assistant && content==""messages before request #26320: Feature request to strip trailingrole=assistant && content==""messages before requests — addresses the empty-trailing-assistant case you describerole:assistantrejected by non-native SDK paths), same two code paths (message-v2.tserror filtering andprompt.tsisLastStepprefill injection)isClaudeModel()helper fix acrosstransform.tsfinish=stop, triggering prefill error on claude-opus-4-6 #17982: OpenCode prompt loop continues afterfinish=stop, triggering prefill error — same prefill error class, sameprompt.tsinjection as root causeYour issue focuses specifically on thinking-on templates (llama.cpp/vLLM
enable_thinking) and proposes a runtime probe of<baseURL>/propsfor auto-detection — that angle appears new. The underlying fix areas (empty-trailing filter intransform.ts,isLastStepprefill inprompt.ts) overlap significantly with the issues above. It may be worth commenting on one of those threads, particularly #19517 which has the most detailed matching root cause analysis.feanor5555 commentedon May 17, 2026
Thanks. Mapped against the 5 cited issues:
filterEmptyContenthelper instead ofisClaudeModel()extraction)prefill:falseper-model)prefill:falsefor Azure-Claude; out of probe scope)finish=stopDistinct contributions of this report:
Failure class. All 5 cited issues are Claude-specific (
@ai-sdk/anthropicor Claude-via-proxy). This report targets thinking-on chat templates (enable_thinkingin Qwen3 / DeepSeek-R1 / GLM-thinking / Kimi-K2-Thinking / MiniMax-M2 on llama.cpp / vLLM / TGI). Different server, different rejection path, same surface error string.Detection mechanism. Runtime probe of
<baseURL>/propsparsingchat_templateforenable_thinking— auto-detects without user config or models.dev entries. Not proposed in any of the cited issues.Empirical validation. 8/8 captured 400-bodies from the original llama.cpp log eliminated by PR1 + PR2 + PR3. Beyond error elimination: thinking-capable models (Qwen3-Thinking, DeepSeek-R1, GLM-thinking, Kimi-K2-Thinking, MiniMax-M2) now complete their reasoning traces uninterrupted — the previous prefill-rejection truncated mid-thought and aborted the step; with
prefill:falserouting the trailing MAX_STEPS marker torole:user, thinking remains active and the model finishes its reasoning before yielding control.Happy to close as duplicate of #26320 if maintainers prefer to consolidate the empty-filter discussion there and keep PR2 / PR3 (prefill schema + probe) under a fresh issue scoped to thinking-templates.
github-actions commentedon Jul 17, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.