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
Step-capped agents fail with an assistant prefill 400 #56250
Select a model behind a provider that does not accept assistant prefill. We saw it with an Anthropic model through the GitLab Duo provider (gitlab/duo-chat-opus-5-5).
Send a prompt that needs more than two tool calls.
Expected: on the last step, the model gets the MAX_STEPS prompt and answers with a text summary.
Result: the last step fails with this provider error (the request_id is removed).
Anthropic API error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"This model does not support assistant message prefill. The conversation must end with a user message."}}
Cause: on the last allowed step, SessionRunner.runStep sends [...messages, Message.assistant(MAX_STEPS_PROMPT)] to request.primary. The request therefore ends on an assistant message.
Suggested fix: send the MAX_STEPS prompt as Message.user(...). Also consider a guard in request.primary that appends a short user turn when the message list ends on an assistant or system message.
Thanks for the clear report and for pinpointing the cause. I reproduced this on 2.0.24, and it still happens on the latest dev (d4a6223).
I set the build agent to steps: 2 and used a simulated model that calls read on step 1. On step 2, the last allowed step, the request OpenCode sends ends with an assistant message containing the "CRITICAL - MAXIMUM STEPS REACHED" prompt. Anthropic models that don't support assistant prefill reject that kind of request with the 400 you saw. The simulated model can't return Anthropic's real error, so it shows a marker in the TUI when a request ends on an assistant message.
The line responsible is still in packages/core/src/session/runner/llm.ts: when the step limit is reached, it appends Message.assistant(MAX_STEPS_PROMPT) to the messages.
The script below reproduces it, and the recording and screenshot show the run.
// Repro for #56250: on the last allowed step (agent `steps: 2`), OpenCode sends a// request whose final message is an assistant turn (MAX_STEPS prompt as prefill).// Providers/models that forbid assistant prefill (e.g. Anthropic Claude via GitLab Duo)// reject such a request with "The conversation must end with a user message".import{Effect,Stream}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constroleOf=(m: any)=>m?.role??m?.typeconstmessagesOf=(body: any): any[]=>body?.messages??body?.input??body?.contents??[]consttextOf=(m: any)=>JSON.stringify(m?.content??m).slice(0,80)exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true},config: {agents: {build: {steps: 2}}}asany,project: {git: true,files: {"a.txt": "A\n","b.txt": "B\n","c.txt": "C\n"}},},({ ui, llm })=>Effect.gen(function*(){yield*llm.serve((request,index)=>{constmsgs=messagesOf(request.body)constlast=msgs[msgs.length-1]constrole=roleOf(last)console.log(`REQUEST ${index+1}: ${msgs.length} messages, last role = ${role}, last = ${textOf(last)}`)if(role==="assistant"){// Stand-in for the provider's documented 400 for prefill-forbidding models.returnStream.make(Llm.text(`PREFILL: request ${index+1} ends with an assistant message -> Anthropic would return 400 "This model does not support assistant message prefill. The conversation must end with a user message."`),)}if(index<3)returnStream.make(Llm.toolCall({index: 0,id: `call_${index}`,name: "read",input: {path: ["a.txt","b.txt","c.txt"][index]}}),Llm.finish("tool-calls"),)returnStream.make(Llm.text("Summary: read the files."))})yield*ui.submit("Read a.txt, b.txt and c.txt one at a time, then summarize.")yield*ui.waitFor(()=>Effect.map(Effect.all([ui.matches("PREFILL:"),ui.matches("Summary:")]),([a,b])=>a||b),{timeout: 60000},)yield*Effect.sleep(1500)yield*ui.screenshot("final")}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Also seen on 2.0.28 with the anthropic provider (claude-sonnet-5-5), on subagents (mode: subagent, steps: 60) launched through the subagent tool. The impact for subagents is worse than a failed turn: the child's file changes up to the cap stay on disk, but the parent receives only the error, no summary, so its work is silently partial (logged as "Failed to drain Session … does not support assistant message prefill"). We hit it on three long-running subagent tasks in one day. Sending the MAX_STEPS prompt as a user message, as suggested above, would fix it for us.
Activity
opencode-agent commentedon Oct 16, 2026
Thanks for the clear report and for pinpointing the cause. I reproduced this on 2.0.24, and it still happens on the latest
dev(d4a6223).I set the
buildagent tosteps: 2and used a simulated model that callsreadon step 1. On step 2, the last allowed step, the request OpenCode sends ends with an assistant message containing the "CRITICAL - MAXIMUM STEPS REACHED" prompt. Anthropic models that don't support assistant prefill reject that kind of request with the 400 you saw. The simulated model can't return Anthropic's real error, so it shows a marker in the TUI when a request ends on an assistant message.The line responsible is still in
packages/core/src/session/runner/llm.ts: when the step limit is reached, it appendsMessage.assistant(MAX_STEPS_PROMPT)to the messages.The script below reproduces it, and the recording and screenshot show the run.
Related, ordered by likelihood:
finish_reason: unknown.recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.tlmquintino commentedon Oct 18, 2026
Also seen on 2.0.28 with the
anthropicprovider (claude-sonnet-5-5), on subagents (mode: subagent,steps: 60) launched through the subagent tool. The impact for subagents is worse than a failed turn: the child's file changes up to the cap stay on disk, but the parent receives only the error, no summary, so its work is silently partial (logged as "Failed to drain Session … does not support assistant message prefill"). We hit it on three long-running subagent tasks in one day. Sending the MAX_STEPS prompt as a user message, as suggested above, would fix it for us.