crewai - ✅(Solved) Fix Claude 4.6 models fail: consecutive assistant messages treated as prefill [4 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
crewAIInc/crewAI#4798Fetched 2026-04-08 00:40:14
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
cross-referenced ×3referenced ×2commented ×1

Claude 4.6 models (Sonnet 4.6, Opus 4.6) return 400 Bad Request errors when used with CrewAI agents. Anthropic made a breaking API change: Claude 4.6 no longer supports assistant message prefilling, and any request with a trailing assistant message is rejected.

Root Cause

CrewAgentExecutor._append_message() appends every LLM response as role: "assistant" (crew_agent_executor.py#L273):

self._append_message(formatted_answer.text)  # role defaults to "assistant"

This builds conversations with consecutive assistant messages:

user: [system prompt + task]
assistant: [LLM response with tool call]
assistant: [tool result + observation]   ← consecutive assistant
assistant: [next iteration]              ← consecutive assistant

Claude 4.6 interprets the trailing assistant message as a prefill attempt and rejects the request. Earlier Claude models (3.5, 4.5) tolerated this, but 4.6 is strict.

Fix Action

Workaround

Using a LiteLLM input callback to merge consecutive messages before they reach the API:

import litellm

def merge_consecutive_messages(kwargs, **_):
    messages = kwargs.get("messages", [])
    if len(messages) < 2:
        return
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            merged[-1]["content"] += "\n\n" + str(msg["content"])
        else:
            merged.append(msg)
    kwargs["messages"] = merged

litellm.input_callback = [merge_consecutive_messages]

PR fix notes

PR #4799: fix: merge consecutive same-role messages for Anthropic Claude 4.6+

Description (problem / solution / changelog)

fix: merge consecutive same-role messages for Anthropic Claude 4.6+

Summary

Claude 4.6+ rejects API requests containing consecutive assistant messages, treating the trailing one as an unsupported prefill. This occurs when CrewAgentExecutor appends multiple assistant messages during tool-use iterations in the ReAct loop (see #4798).

This PR adds a _merge_consecutive_messages() static method on BaseLLM that collapses runs of consecutive same-role messages into a single message by joining their string content with \n\n. Messages with list content (e.g. tool-use blocks, multimodal content) are left untouched.

The merge is called in both LLM provider paths:

  • LLM._format_messages_for_provider() — LiteLLM path
  • AnthropicCompletion._format_messages_for_anthropic() — native Anthropic path

7 unit tests added covering merging, preservation of alternating messages, list-content skipping, immutability, non-Anthropic passthrough, and user-message merging.

Fixes #4798

Review & Testing Checklist for Human

  • List-content edge case: When a tool-use block (list content) is followed by a string assistant message, they are not merged — consecutive same-role messages can still remain after merging. Verify this doesn't re-trigger the Claude 4.6+ error in real tool-use scenarios where tool_use content blocks precede text assistant messages.
  • Ordering in native path: The merge runs after tool-result formatting but before the "ensure first message is user" check in completion.py:626-638. Confirm this ordering doesn't interfere with tool result message structure.
  • Separator choice: Content is joined with \n\n. Verify this doesn't break any downstream parsing that relies on message content structure (e.g., ReAct output parsing, structured output extraction).
  • E2E validation: Run a real multi-tool agent workflow against Claude 4.6+ (e.g. anthropic/claude-sonnet-4-6-20250514) to confirm the 400 Bad Request error from the issue is resolved.

Notes

  • The merge applies unconditionally to all Anthropic models (not gated to 4.6+ specifically), which should be safe since merging is a no-op for already-alternating messages.
  • The merge applies to all roles (user, assistant, system), not just assistant.

Requested by: João Link to Devin Session

<!-- CURSOR_SUMMARY -->

[!NOTE] Low Risk Localized message-formatting change gated to Anthropic models with comprehensive unit tests; main risk is subtle prompt/formatting differences due to concatenation.

Overview Fixes Anthropic/Claude 4.6+ request failures by merging consecutive same-role messages before sending them to Anthropic APIs.

Adds BaseLLM._merge_consecutive_messages() (string-only merge with \n\n, leaving list/tool/multimodal blocks untouched) and invokes it in both the LiteLLM path (LLM._format_messages_for_provider) and the native Anthropic path (AnthropicCompletion._format_messages_for_anthropic). Expands unit coverage with new tests validating merge behavior, non-Anthropic passthrough, list-content skipping, and immutability.

<sup>Written by Cursor Bugbot for commit c2e67471762d0f5207c2c1355723f6a6f16b6278. This will update automatically on new commits. Configure here.</sup>

<!-- /CURSOR_SUMMARY -->

Changed files

  • lib/crewai/src/crewai/llm.py (modified, +6/-0)
  • lib/crewai/src/crewai/llms/base_llm.py (modified, +37/-0)
  • lib/crewai/src/crewai/llms/providers/anthropic/completion.py (modified, +6/-0)
  • lib/crewai/tests/test_llm.py (modified, +144/-0)

PR #1: fix(anthropic): insert user message between consecutive assistant mes…

Description (problem / solution / changelog)

…sages

Fixes #4798. Claude rejects consecutive assistant messages. The Anthropic formatter now inserts a synthetic user message between them, similar to the existing Bedrock approach.

Changed files

  • lib/crewai/src/crewai/llms/providers/anthropic/completion.py (modified, +17/-1)
  • lib/crewai/tests/llms/anthropic/test_anthropic.py (modified, +34/-0)

PR #4956: fix(anthropic): insert user message between consecutive assistant messages

Description (problem / solution / changelog)

Fixes #4798 - Claude rejects consecutive assistant messages in conversation history. During the ReAct loop, CrewAI can create assistant-assistant sequences. The Anthropic formatter now inserts a synthetic user message between them, similar to the existing Bedrock approach.

Changed files

  • lib/crewai/src/crewai/llms/providers/anthropic/completion.py (modified, +17/-1)
  • lib/crewai/tests/llms/anthropic/test_anthropic.py (modified, +34/-0)

PR #542: Fix Claude 4.6 assistant message prefill crash

Description (problem / solution / changelog)

Summary

Claude 4.6 removed support for assistant message prefill — any request where the messages array ends with role: "assistant" is rejected with BadRequestError. This crashed reconcile runs (observed at iteration 27 on bridge-analysis PR #336).

The root cause is not fully clear — our code appends tool results after each assistant message, so messages should end with role: "tool". However, something in LiteLLM's Anthropic message translation appears to produce a trailing assistant turn in edge cases on longer conversations.

Fixes:

  1. Prefill guard before each completion() call: if messages end with role: "assistant", inject a user message to fix the sequence. Logs when triggered for debugging.
  2. Catch BadRequestError (previously uncaught → unhandled crash). Logs last 5 message roles on prefill errors for root cause diagnosis.

Applied to both reconcile.py and resolve.py.

Fixes #541

Test plan

  • All 440 tests pass (excluding 3 pre-existing failures from #533)
  • Re-run reconcile on bridge-analysis PR #336 and verify it doesn't crash

🤖 Generated with Claude Code

Changed files

  • lib/reconcile.py (modified, +23/-0)
  • lib/resolve.py (modified, +19/-0)

Code Example

self._append_message(formatted_answer.text)  # role defaults to "assistant"

---

user: [system prompt + task]
assistant: [LLM response with tool call]
assistant: [tool result + observation]   ← consecutive assistant
assistant: [next iteration]              ← consecutive assistant

---

400 Bad Request: Prefilling assistant messages is no longer supported

---

# Handle Anthropic models — merge consecutive same-role messages
if self.is_anthropic:
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            merged[-1]["content"] += "\n\n" + str(msg["content"])
        else:
            merged.append(msg)
    messages = merged

---

import litellm

def merge_consecutive_messages(kwargs, **_):
    messages = kwargs.get("messages", [])
    if len(messages) < 2:
        return
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            merged[-1]["content"] += "\n\n" + str(msg["content"])
        else:
            merged.append(msg)
    kwargs["messages"] = merged

litellm.input_callback = [merge_consecutive_messages]
RAW_BUFFERClick to expand / collapse

Description

Claude 4.6 models (Sonnet 4.6, Opus 4.6) return 400 Bad Request errors when used with CrewAI agents. Anthropic made a breaking API change: Claude 4.6 no longer supports assistant message prefilling, and any request with a trailing assistant message is rejected.

Root Cause

CrewAgentExecutor._append_message() appends every LLM response as role: "assistant" (crew_agent_executor.py#L273):

self._append_message(formatted_answer.text)  # role defaults to "assistant"

This builds conversations with consecutive assistant messages:

user: [system prompt + task]
assistant: [LLM response with tool call]
assistant: [tool result + observation]   ← consecutive assistant
assistant: [next iteration]              ← consecutive assistant

Claude 4.6 interprets the trailing assistant message as a prefill attempt and rejects the request. Earlier Claude models (3.5, 4.5) tolerated this, but 4.6 is strict.

Steps to Reproduce

  1. Create a CrewAI agent with llm=LLM(model="anthropic/claude-sonnet-4-6-20250514")
  2. Give it a task that requires tool use (multiple iterations)
  3. The agent fails on the second LLM call with:
    400 Bad Request: Prefilling assistant messages is no longer supported

Expected Behavior

CrewAI should normalize the message history before sending to the LLM — either by:

  • Merging consecutive same-role messages
  • Inserting a user message between consecutive assistant messages
  • Using _format_model_specific_messages() to handle this (it already has Mistral/Ollama handling but not Anthropic)

Environment

  • CrewAI version: 1.6.1 (also reproduced conceptually against 1.10.1 source)
  • Python: 3.13
  • LLM provider: OpenRouter (LiteLLM path)
  • Models affected: anthropic/claude-sonnet-4-6-20250514, anthropic/claude-opus-4-6-20250514

Related Issues

  • #1454 (alternating messages in instruct mode — fixed via LiteLLM ensure_alternating_roles, but that param is no longer available)
  • #2063 (Anthropic message formatting — fixed first-message ordering but not consecutive assistant messages)
  • #3964 (extended thinking — fixed thinking block preservation)
  • External: livekit/agents#4907, strands-agents/sdk-python#1694

Suggested Fix

Add Anthropic handling to _format_model_specific_messages() in llm.py (around line 1508) to merge consecutive assistant messages, similar to the existing Mistral/Ollama handling:

# Handle Anthropic models — merge consecutive same-role messages
if self.is_anthropic:
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            merged[-1]["content"] += "\n\n" + str(msg["content"])
        else:
            merged.append(msg)
    messages = merged

Workaround

Using a LiteLLM input callback to merge consecutive messages before they reach the API:

import litellm

def merge_consecutive_messages(kwargs, **_):
    messages = kwargs.get("messages", [])
    if len(messages) < 2:
        return
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            merged[-1]["content"] += "\n\n" + str(msg["content"])
        else:
            merged.append(msg)
    kwargs["messages"] = merged

litellm.input_callback = [merge_consecutive_messages]

extent analysis

Fix Plan

To resolve the issue with Claude 4.6 models returning 400 Bad Request errors, we need to modify the CrewAgentExecutor to handle consecutive assistant messages.

Here are the steps:

  • Modify the _format_model_specific_messages() function in llm.py to merge consecutive same-role messages for Anthropic models.
  • Add a check to identify Anthropic models and apply the merging logic.

Code Changes

# In llm.py, around line

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING