Skip to content

Native LLM: trailing assistant prefill (MAX_STEPS_PROMPT) triggers a hard 400 on Claude 4.6+/5.x for step-limited agents #40455

Closed as not planned

Description

@netbrah

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.

Activity

  1. github-actions commented on Aug 4, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Your analysis of the native LLM path (OPENCODE_EXPERIMENTAL_NATIVE_LLM) adds useful specificity around the llm.ts:211 injection point, which complements but largely overlaps these existing reports.

  2. github-actions commented on Oct 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions