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.
On this page

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:
{
"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-5and fails onclaude-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.
| Model | Final assistant turn (prefill) | Forced tool_choice (any / tool) | Structured outputs |
|---|---|---|---|
| Opus 5.5 | 400 | 400 | Yes |
| Opus 5, Opus 4.8, Opus 4.7, Opus 4.6 | 400 | Accepted | Yes |
| Sonnet 5.5 | 400 | 400 | Yes (not on Amazon Bedrock) |
| Sonnet 5, Sonnet 4.6 | 400 | Accepted | Yes |
| Fable 5.1 | 400 | 400 | Yes |
| Fable 5 | 400 | Accepted | Yes |
| Mythos Preview, Mythos 5, Mythos 5.1 | 400 | 400 on Mythos 5.1 | Yes |
| Sonnet 4.5, Haiku 4.5, Opus 4.5 | Accepted | Accepted | Yes |
| Older models | Accepted | Accepted | Not 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 did | Example prefill | Replacement |
|---|---|---|
| Force an output format | { or [ | JSON outputs via output_config.format, or a strict tool |
| Force a label or category | Category: | enum field in a JSON schema, or a tool with enum fields |
| Skip the preamble | Here is the summary: | System prompt: "Answer directly, without a preamble" |
| Head off unwanted refusals | Sure, here are the steps: | Clear instructions in the user message |
| Continue an interrupted answer | the partial reply so far | User 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 |

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:
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:
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:
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. Checkstop_reasonbefore 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:
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:
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."
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.

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:
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:
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:
- Before every provider call, drop trailing assistant turns that have no text or tool content.
- If a non-empty assistant turn would be last, keep it in history and append a user message with the continuation instruction.
- If there is no new user input at all, don't send the request.
- 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 it | What it looks like |
|---|---|
| Claude API and SDKs | 400 invalid_request_error with the full message |
| LiteLLM | litellm.BadRequestError: AnthropicException - {...} with the message inside |
| GitHub Copilot endpoint (via opencode) | the message with "code":"invalid_request_body" |
| OpenRouter | Provider returned error; the upstream message appears in logs |
| Amazon Bedrock | ValidationException |
| Databricks FMAPI | HTTP 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:
- Update the tool. Fixes land in tool releases, not in your settings. Check the changelog for "prefill" or "trailing assistant".
- 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.
- Start a new conversation or session. This clears a transcript stuck on a stray assistant turn.
- 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.
- 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 contains | Cause | Fix |
|---|---|---|
does not support assistant message prefill | Last 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.1 | tool_choice auto, a strict tool, and an instruction in the prompt |
`thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified | Thinking blocks edited, reordered, or filtered before being sent back | Pass thinking blocks back exactly as received |
"thinking.type.enabled" is not supported for this model | Manual thinking budget on Claude 4.7 or later | Adaptive thinking plus output_config.effort |
temperature or top_p named in the message | Non-default sampling parameters on Opus 4.7 or later | Claude 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_result | The user turn after a tool call lacks its result | Send 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.
Sources9
External pages this guide links to, in the order they appear. Last updated Oct 5, 2026.
- 1.Anthropic's error referenceplatform.claude.com/docs/en/api/errors
- 2.Opus 5.5platform.claude.com/docs/en/models/opus-5-5/migration-guide
- 3.Sonnet 5.5platform.claude.com/docs/en/models/sonnet-5-5/migration-guide
- 4.Fable 5.1platform.claude.com/docs/en/models/fable-5-1/migration-guide
- 5.structured outputs pageplatform.claude.com/docs/en/build-with-claude/structured-outputs
- 6.PR #30242github.com/BerriAI/litellm/pull/30242
- 7.PR #3027github.com/dyad-sh/dyad/pull/3027
- 8.OpenClaw pull request (#69268)github.com/openclaw/openclaw/pull/69268
- 9.opencode issue #13768github.com/anomalyco/opencode/issues/13768





