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

Open
#40,455 1 comment 0 reactions 1 assignee View on GitHub

@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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.