anomalyco / anomalyco/opencode
Native LLM: trailing assistant prefill (MAX_STEPS_PROMPT) triggers a hard 400 on Claude 4.6+/5.x for step-limited agents
@jlongster is already working on this.
Since Aug 4, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Summary
On the native @opencode-ai/llm path (behind OPENCODE_EXPERIMENTAL_NATIVE_LLM), a step-limited agent on its final step sends a trailing assistant-role message to Anthropic. Claude 4.6+ / 5.x models (including claude-opus-4-8, which opencode explicitly targets) reject a request whose final message has role assistant ("assistant message prefill") with a hard 400. So a step-limited agent's last turn fails outright on those models.
I found this by comparing opencode's native path against qwen-code (a gemini-cli fork), which hit and fixed the identical issue in real dogfooding. I have not runtime-reproduced it against opencode — this is a static trace — but the mechanism is concrete and cited below.
Mechanism
On the last step, the runner appends Message.assistant(MAX_STEPS_PROMPT) as the final message in the request (session/runner/llm.ts:211):
const isLastStep = agent.info?.steps !== undefined && currentStep >= agent.info.steps // :202
...
messages: [...toLLMMessages(context, model), ...(isLastStep ? [Message.assistant(MAX_STEPS_PROMPT)] : [])], // :211
toolChoice: isLastStep ? "none" : undefined, // :213
The Anthropic adapter lowers this to a trailing assistant message with no strip/guard for models that reject prefill (protocols/anthropic-messages.ts:442-469), and opencode explicitly supports claude-opus-4-8 (anthropic-messages.ts:356).
Per Anthropic's own migration guidance, Claude 4.6+ and every 5.x model reject a request whose final message is an assistant prefill with a hard 400 — this is a model-generation behavior change, identical on the native API, Vertex AI, and Bedrock.
Trigger
An agent configured with a step limit (agent.info.steps), on its final step, using a Claude 4.6+/5.x model through the native path. Agents without a configured step limit never take the isLastStep branch, so this is scoped to step-limited agents (e.g. subagents with a max-steps budget).
Expected vs. observed
- Expected: the last-step nudge is delivered in a way the target model accepts.
- Observed (traced, not runtime-reproduced on opencode): for Claude 4.6+/5.x the final assistant prefill message is rejected with a hard 400.
Prior art
qwen-code fixed this by stripping the assistant-turn prefill for 4.6+ models — see QwenLM/qwen-code#8040 ("strip assistant-turn prefill and fix thinking.display on 4.6+ models").
Suggested direction (non-prescriptive)
For model generations that reject a trailing assistant prefill, either deliver MAX_STEPS_PROMPT as a user message on the last step, or omit/convert the prefill for those models. The preferred shape (keep prefill semantics where supported vs. normalize everywhere) is a maintainer call. Happy to help, though I can't dogfood-test opencode myself.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.