Skip to main content

Claude "Does Not Support Assistant Message Prefill" 400 Fix

Claude 4.6 and later models reject any request that ends with an assistant turn; Sonnet 4.5 and Haiku 4.5 still accept one. Replace the prefill by its job.

LaoZhang AI TeamPublishedUpdated 13 min read
On this page
Illustration of a Claude request whose trailing assistant turn triggers a prefill error, with earlier assistant history preserved and a user message shown as the required ending

The full error is This model does not support assistant message prefill. The conversation must end with a user message. It comes back as a 400 invalid_request_error whenever the last item in messages has role: "assistant", and the model you called is Claude 4.6 or later: Opus 4.6 through Opus 5.5, Sonnet 4.6 through Sonnet 5.5, Fable 5 and 5.1, and Claude Mythos Preview. Claude Sonnet 4.5, Claude Haiku 4.5, and older models still accept a prefill.

The fix has two parts. Make the request end with a user message, then move whatever the prefill was doing somewhere the new models accept: a JSON schema in output_config.format, an instruction in the system prompt, or text in the user turn. If the error appears inside Copilot, opencode, an agent framework, or a router, the tool built that trailing assistant turn, not you. The sections on frameworks and closed tools cover what you can still do in that case.

What "assistant message prefill" means in this 400

A prefill is an assistant message placed at the end of messages. On older models, Claude treated that text as the start of its own reply and continued from it. That was the usual way to force JSON (prefill {), skip a preamble, or pick up a reply that was cut off.

On the models listed above, the API rejects the request before generating anything. According to Anthropic's error reference, the response body looks like this:

json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "This model does not support assistant message prefill. The conversation must end with a user message."
  }
}

Three details decide whether this is your problem:

  • Only the last message matters. Earlier assistant turns in the history are normal conversation and stay allowed. The rule is that the final message must be user.
  • Content doesn't matter. An empty assistant message, an aborted turn, or a full paragraph all count. Several agent bugs below come from an assistant turn with no text at all.
  • The model ID matters. The same code works on claude-haiku-4-5 and fails on claude-sonnet-4-6. That is why the error often appears right after a model upgrade or when a tool silently switches to a newer default.

To confirm, log the outgoing request and check the role of its last message. If it says assistant, you have found the cause.

Which Claude models reject prefill and which still accept it

Anthropic's error reference states the rule as "Claude 4.6 and later models and Claude Mythos Preview do not support prefilling assistant messages." The per-model migration guides for Opus 5.5, Sonnet 5.5, and Fable 5.1 confirm it model by model. The table also shows forced tool_choice, because the classic workaround of "force a tool call instead of prefilling" fails on the newest models too.

ModelFinal assistant turn (prefill)Forced tool_choice (any / tool)Structured outputs
Opus 5.5400400Yes
Opus 5, Opus 4.8, Opus 4.7, Opus 4.6400AcceptedYes
Sonnet 5.5400400Yes (not on Amazon Bedrock)
Sonnet 5, Sonnet 4.6400AcceptedYes
Fable 5.1400400Yes
Fable 5400AcceptedYes
Mythos Preview, Mythos 5, Mythos 5.1400400 on Mythos 5.1Yes
Sonnet 4.5, Haiku 4.5, Opus 4.5AcceptedAcceptedYes
Older modelsAcceptedAcceptedNot in the supported list

Sources: the error reference, the Opus 5.5, Sonnet 5.5, and Fable 5.1 migration guides, and the supported-model list on the structured outputs page, all as of October 5, 2026. Mythos 5 and 5.1 are covered by the "Claude 4.6 and later" rule rather than by a dedicated prefill line. Model availability changes, so check the current model list before you pin an older ID as a fallback.

Two practical readings of this table:

  • Sonnet 4.5, Haiku 4.5, and Opus 4.5 are the newest models that still accept prefill. Pointing a tool at one of them is a valid short-term workaround, but it is not a migration.
  • Every replacement below also works on Sonnet 4.5, Haiku 4.5, and Opus 4.5, since they support structured outputs. You can remove prefill from all code paths at once instead of keeping a model-specific branch.

Replace the prefill by what it was doing

There is no single drop-in replacement, because prefill did different jobs in different code. The Sonnet 5.5 migration guide maps each job to its replacement:

What the prefill didExample prefillReplacement
Force an output format{ or [JSON outputs via output_config.format, or a strict tool
Force a label or categoryCategory:enum field in a JSON schema, or a tool with enum fields
Skip the preambleHere is the summary:System prompt: "Answer directly, without a preamble"
Head off unwanted refusalsSure, here are the steps:Clear instructions in the user message
Continue an interrupted answerthe partial reply so farUser message: "Your previous response was interrupted and ended with … Continue from where you left off."
Remind the model of context(Remember: answer in French)Put the reminder in the user turn

Three routes for replacing prefill: a JSON schema in output_config.format, a system prompt for direct prose, or a strict classification tool with tool_choice auto

The code below follows the request shapes in Anthropic's documentation as of October 5, 2026. Run one call against your own model ID before you ship it.

Forcing JSON: output_config.format instead of a { prefill

This is the most common prefill. The old request ended with {"role": "assistant", "content": "{"}. The new one ends with the user message and carries a JSON schema:

python
import json
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "List the top 3 programming languages by popularity"}
    ],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "languages": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "rank": {"type": "integer"}
                            },
                            "required": ["name", "rank"],
                            "additionalProperties": False
                        }
                    }
                },
                "required": ["languages"],
                "additionalProperties": False
            }
        }
    },
)

# Thinking blocks can come first on newer models, so pick the text block by type
text = next(block.text for block in response.content if block.type == "text")
data = json.loads(text)

Two changes from older examples matter here. First, on models where thinking is always on, such as Opus 5.5, response.content[0] may be a thinking block, so response.content[0].text breaks. Select the text block by type. Second, every object in the schema needs "additionalProperties": false. The API rejects other values with a 400 that names the problem.

With the SDK helpers you pass a type instead of a raw schema. In Python, client.messages.parse() takes a Pydantic model:

python
from pydantic import BaseModel

class Language(BaseModel):
    name: str
    rank: int

class LanguageList(BaseModel):
    languages: list[Language]

response = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "List the top 3 programming languages by popularity"}],
    output_format=LanguageList,
)
print(response.parsed_output)

In TypeScript, wrap a Zod schema in zodOutputFormat(). Passing the bare Zod object as schema doesn't work:

typescript
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

const client = new Anthropic();

const LanguageList = z.object({
  languages: z.array(z.object({ name: z.string(), rank: z.number() })),
});

const response = await client.messages.parse({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "List the top 3 programming languages by popularity" }],
  output_config: { format: zodOutputFormat(LanguageList) },
});
console.log(response.parsed_output);

The Python helper's output_format= argument is part of the SDK, not the API. Inside a raw request, the old top-level output_format parameter is deprecated. Per the structured outputs page, it now returns a 400 unless you add the structured-outputs-2025-11-13 beta header, so send output_config.format instead.

Limits worth knowing before you switch:

  • JSON outputs can't be combined with citations. A request with both returns 400.
  • The first request with a new schema has extra latency while the grammar compiles. Compiled grammars are cached for 24 hours from last use.
  • A safety refusal (stop_reason: "refusal") or a cut-off reply (stop_reason: "max_tokens") can return output that doesn't match the schema. Check stop_reason before you parse.

Classification labels: an enum schema, or a strict tool with tool_choice auto

If the prefill was Category: and you parsed one word, put the allowed values in an enum field of a JSON schema. That is the shortest path, and it works on every model in the structured outputs list.

If you prefer a tool, don't replace the prefill with a forced tool call. On Opus 5.5, Sonnet 5.5, and Fable 5.1, tool_choice of type any or tool returns its own 400: tool_choice: type "tool" and "any" are not supported for this model. The migration guides recommend leaving tool_choice at auto, setting strict: true, and saying in the prompt when to use the tool:

python
response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    tools=[{
        "name": "record_category",
        "description": "Record the category of a support ticket",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "category": {"type": "string", "enum": ["billing", "bug", "feature_request", "other"]}
            },
            "required": ["category"],
            "additionalProperties": False
        }
    }],
    tool_choice={"type": "auto"},
    messages=[{
        "role": "user",
        "content": "Classify this ticket with the record_category tool: 'I was charged twice this month.'"
    }],
)

With auto, the model can still answer in plain text, so handle a response that has no tool_use block. Structured outputs don't guarantee the capitalization of enum values, so compare labels case-insensitively. JSON outputs and strict tools can be used in the same request when an agent needs both.

Skipping the preamble: instructions in the system prompt

A prefill such as ## Bug Analysis\n\n or Here is the summary: forced the opening of the reply. Put that requirement in the system prompt and end the request with the user message:

python
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    system=(
        "You are a code review assistant. Answer directly, with no introduction. "
        "Start with the heading '## Bug Analysis', then list each bug with a severity "
        "and a suggested fix."
    ),
    messages=[{"role": "user", "content": "Analyze this function for bugs: ..."}],
)

The system prompt gives no schema guarantee. If downstream code parses the reply, use JSON outputs instead. If the reply is read by a person, the system prompt is the simplest replacement and works alongside citations, which JSON outputs don't. On Fable 5.1, the migration guide recommends system prompt instructions as the replacement for prefill.

Continuing an interrupted answer: move the partial text into the user turn

Prefill used to be the way to resume a reply that stopped on max_tokens, a timeout, or a dropped stream: send the partial text back as an assistant message and let Claude keep writing. On the new models, keep the history, then end with a user message that quotes where the reply stopped. Anthropic's suggested wording is "Your previous response was interrupted and ended with [previous_response]. Continue from where you left off."

python
CONTINUE = (
    "Your previous response was interrupted and ended with:\n\n{tail}\n\n"
    "Continue from where you left off."
)

def continuation_request(history, partial_text):
    """history ends with the user turn that produced partial_text."""
    return history + [
        {"role": "assistant", "content": partial_text},
        {"role": "user", "content": CONTINUE.format(tail=partial_text[-400:])},
    ]

The partial reply stays in the history as a normal assistant turn, and the request now ends with a user message, so it is valid. The API no longer joins the two pieces for you. Stitch the continuation to the saved partial text in your own code, and check the seam for a repeated word or sentence.

Continuation diagram that keeps a partial assistant reply in history, appends a user instruction, and joins the saved text with the new continuation in the application

Refusal steering and context reminders: say it in the user message

Prefills such as Sure, here's how: were meant to stop the model from declining a legitimate request. The Sonnet 5.5 guide says that clear instructions in the user message are usually enough, so spell out the task and the output you expect there instead of putting words in the model's mouth. Reminders that used to ride in a prefill, such as language, persona, or constraints from earlier in the conversation, also go in the user turn.

Amazon Bedrock: no structured outputs on Claude in Amazon Bedrock

The replacements above assume structured outputs are available, and on Bedrock that depends on the integration. According to the structured outputs page, the legacy "Amazon Bedrock (Opus 4.6 and earlier)" integration supports them for Opus 4.6, Sonnet 4.6, Sonnet 4.5, Opus 4.5, and Haiku 4.5. The newer "Claude in Amazon Bedrock" integration does not. The Sonnet 5.5 guide repeats this for Sonnet 5.5 on Bedrock.

On that integration, replace a prefill with system or user instructions, or with a tool definition without strict: true, and validate the output in your code. If the model ignores the format now and then, a validation-and-retry loop is your guarantee. Bedrock reports request errors as ValidationException, so the prefill message may arrive inside that wrapper. For other Bedrock 400s, see AWS Bedrock Claude 400 Error: Match the Exact Message to the Fix.

Migrating a codebase: find every trailing assistant turn

Prefill is often hidden in a helper that builds messages, so searching for model IDs won't find it. Search for code that appends an assistant message right before the API call:

bash
grep -rnE "role[\"']?[[:space:]]*[:=][[:space:]]*[\"']assistant" src/

Review each hit and ask whether that message can end up last in the request. Then add a guard where requests are built, so a regression fails in tests instead of production:

python
def assert_ends_with_user(messages):
    if messages and messages[-1]["role"] == "assistant":
        raise ValueError(
            "Request ends with an assistant turn; Claude 4.6 and later models "
            "reject this with 'does not support assistant message prefill'."
        )

Don't gate the guard on a hardcoded list of model IDs. The list has grown with every release since Opus 4.6, and the replacements work on the older models too.

If you use Claude Code, the migration guides describe a bundled Claude API skill that does this work. Run /claude-api migrate this project to claude-sonnet-5-5 (or your target model). The skill swaps model IDs and applies breaking parameter changes, prefill replacement, and effort calibration. It asks you to confirm the scope before editing files and ends with a checklist of items to verify by hand.

Moving to Opus 4.7 or later also brings other 400s, such as non-default sampling parameters and manual thinking budgets. Claude Opus 4.7 vs 4.6: API Migration, Token Costs, and When to Upgrade covers the full list for that step.

Why agent frameworks and routers hit the prefill 400

When the error comes from a library rather than your own request, it usually traces to one of two patterns. Both are public in issue trackers.

Pattern A: resuming or falling back by prefill. A router or agent that retries after an interrupted stream sends the partial reply back as an assistant message. That worked on older models and now fails on every hop. LiteLLM's mid-stream fallback did exactly this. PR #30242 (opened June 11, 2026, still open as of October 5, 2026) describes every fallback hop on Sonnet and Opus 4.6+ returning litellm.BadRequestError: AnthropicException - {"type":"invalid_request_error","message":"This model does not support assistant message prefill. ..."}. It also lists the same breakage in livekit/agents#4907, crewAIInc/crewAI#4798, and agno-agi/agno#7015. The proposed fix marks those models supports_assistant_prefill: false and sends the partial reply in a trailing user message, which is Anthropic's continuation pattern. Dyad shipped the same change in PR #3027 (merged March 23, 2026). Its continuation retry now sends a user message containing the partial response.

Pattern B: the transcript ends on an empty or stray assistant turn. The client stores an assistant message with no text, for example after an aborted step, a heartbeat, or a UI event. It then sends the transcript with no fresh user input behind it. An OpenClaw pull request (#69268) describes this cause: a session whose tail was an assistant turn with no new user input. It proposed skipping the call in that state, on top of a patch that drops trailing empty or aborted assistant turns before sending. In opencode issue #13768, contributors traced several reports to empty assistant messages left at the end of the prompt.

If you maintain the framework, the fix list is short:

  1. Before every provider call, drop trailing assistant turns that have no text or tool content.
  2. If a non-empty assistant turn would be last, keep it in history and append a user message with the continuation instruction.
  3. If there is no new user input at all, don't send the request.
  4. Track prefill support as a per-model capability that defaults to "not supported" for unknown Claude models. A default of "supported" breaks again with each new release.

Dyad's PR also notes that some non-Anthropic providers reject an assistant-final request too, so the user-message continuation is the safer default for every provider.

Copilot, opencode, Databricks: what to do when you can't edit the request

Public reports of this error come from GitHub Copilot (Visual Studio Developer Community), opencode, Antigravity (Google AI Developers Forum, March 29, 2026), Databricks Foundation Model APIs (May 11, 2026), LibreChat agents (May 19, 2026), and OpenHands with claude-opus-5-5 (September 2026). In all of them the tool builds the conversation, so the fix has to come from the tool. The opencode issue was auto-closed on September 2, 2026, and a comment on October 2, 2026 reports the error still happening on Opus 5.5.

The message often arrives wrapped, which makes it harder to search for:

Where you see itWhat it looks like
Claude API and SDKs400 invalid_request_error with the full message
LiteLLMlitellm.BadRequestError: AnthropicException - {...} with the message inside
GitHub Copilot endpoint (via opencode)the message with "code":"invalid_request_body"
OpenRouterProvider returned error; the upstream message appears in logs
Amazon BedrockValidationException
Databricks FMAPIHTTP 400 BAD_REQUEST with the message

One opencode contributor also reported that a streaming request through a custom gateway came back as a 400 with an empty body. Replaying the same request without streaming showed the prefill message. If you see a bare 400 while streaming, that replay is a quick way to read the actual message.

Without access to the code, these are your options, roughly in order of effort:

  1. Update the tool. Fixes land in tool releases, not in your settings. Check the changelog for "prefill" or "trailing assistant".
  2. Send a new message. The conversation then ends with a user turn again. opencode users report that typing "continue" or "proceed" sometimes clears the error but not reliably, because the tool may append another assistant turn.
  3. Start a new conversation or session. This clears a transcript stuck on a stray assistant turn.
  4. Switch the model the tool uses. Sonnet 4.5, Haiku 4.5, and Opus 4.5 still accept prefill, and a non-Claude model avoids this Claude rule, although some other providers reject assistant-final requests too.
  5. Report it to the vendor with evidence. Attach a session export or request log that shows the last outgoing message with role: "assistant". That points to the cause faster than the error text alone.

Other Claude 400 errors that look like the prefill error

All of these are invalid_request_error with status 400. The message text tells them apart:

Message containsCauseFix
does not support assistant message prefillLast message has role: "assistant"End with a user message; replace the prefill by its job
tool_choice: type "tool" and "any" are not supported for this model.Forced tool call on Opus 5.5, Sonnet 5.5, Fable 5.1, or Mythos 5.1tool_choice auto, a strict tool, and an instruction in the prompt
`thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modifiedThinking blocks edited, reordered, or filtered before being sent backPass thinking blocks back exactly as received
"thinking.type.enabled" is not supported for this modelManual thinking budget on Claude 4.7 or laterAdaptive thinking plus output_config.effort
temperature or top_p named in the messageNon-default sampling parameters on Opus 4.7 or laterClaude Code Opus 4.7 top_p Deprecated? Fix the 400 Without Guesswork and Claude Opus 4.7 Temperature Parameter: Remove It, Do Not Retune It
A tool_use ID without a matching tool_resultThe user turn after a tool call lacks its resultSend a tool_result for every tool_use before adding new text

The last row matters if you fix Pattern A by appending a user message. If the trailing assistant turn contains a tool_use block, a plain-text user message trades the prefill 400 for the missing-result 400. Return the tool result first.

A 500 is different: it is a server-side failure, not a problem with the request, and changing messages won't fix it. See Claude API Error 500: Handle Internal Server Errors and Persistent Failures.

Sources9

External pages this guide links to, in the order they appear. Last updated Oct 5, 2026.

  1. 1.Anthropic's error referenceplatform.claude.com/docs/en/api/errors
  2. 2.Opus 5.5platform.claude.com/docs/en/models/opus-5-5/migration-guide
  3. 3.Sonnet 5.5platform.claude.com/docs/en/models/sonnet-5-5/migration-guide
  4. 4.Fable 5.1platform.claude.com/docs/en/models/fable-5-1/migration-guide
  5. 5.structured outputs pageplatform.claude.com/docs/en/build-with-claude/structured-outputs
  6. 6.PR #30242github.com/BerriAI/litellm/pull/30242
  7. 7.PR #3027github.com/dyad-sh/dyad/pull/3027
  8. 8.OpenClaw pull request (#69268)github.com/openclaw/openclaw/pull/69268
  9. 9.opencode issue #13768github.com/anomalyco/opencode/issues/13768
More in API Guides
A decision map comparing a direct typed response with application-executed tools and a final typed object
API Guides

Structured Outputs vs Function Calling: Choose the Right OpenAI API Contract

Use text.format when your application needs the model's final typed answer. Use function calling when the answer requires application data or an action. Combine them when a real tool result must become a schema-controlled object; then check both the schema and the business result.

11 min