Aider-AI/aider · error · ValueError
Messages don't properly alternate user/assistant: {turns}
Error message
Messages don't properly alternate user/assistant:
{turns} What it means
sanity_check_messages in aider/sendchat.py walks the message list and raises ValueError whenever two consecutive non-system messages share the same role — the list must strictly alternate user/assistant (system messages may appear anywhere). The message dumps the full formatted transcript (format_messages) so you can see the exact turn where duplication occurs. This check runs when AIDER_SANITY_CHECK_TURNS is set (send_completion) and feeds guard logic for models that require strict alternation (e.g. DeepSeek R1 via ensure_alternating_roles).
Source
Thrown at aider/sendchat.py:20
from aider.utils import format_messages
def sanity_check_messages(messages):
"""Check if messages alternate between user and assistant roles.
System messages can be interspersed anywhere.
Also verifies the last non-system message is from the user.
Returns True if valid, False otherwise."""
last_role = None
last_non_system_role = None
for msg in messages:
role = msg.get("role")
if role == "system":
continue
if last_role and role == last_role:
turns = format_messages(messages)
raise ValueError("Messages don't properly alternate user/assistant:\n\n" + turns)
last_role = role
last_non_system_role = role
# Ensure last non-system message is from user
return last_non_system_role == "user"
def ensure_alternating_roles(messages):
"""Ensure messages alternate between 'assistant' and 'user' roles.
Inserts empty messages of the opposite role when consecutive messages
of the same role are found.
Args:
messages: List of message dictionaries with 'role' and 'content' keys.
Returns:View on GitHub (pinned to 5dc9490bb3)
Solutions
- Run ensure_alternating_roles(messages) (same module) before sending — it inserts empty opposite-role messages to repair the list.
- Find the duplicated turn in the dumped transcript in the error message and merge or remove the redundant same-role message at your construction site.
- If you only wanted the boolean, call it without AIDER_SANITY_CHECK_TURNS set; note the function returns True/False for the last-message check but raises for alternation violations.
Example fix
# before
messages = [
{"role": "user", "content": "hi"},
{"role": "user", "content": "add a test"}, # raises
]
send(messages)
# after
from aider.sendchat import ensure_alternating_roles
messages = ensure_alternating_roles(messages) # inserts {"role":"assistant","content":""} between them
send(messages) Defensive patterns
Strategy: validation
Validate before calling
from aider.sendchat import ensure_alternating_roles, sanity_check_messages
def prep_messages(messages):
messages = ensure_alternating_roles(messages) # repairs consecutive same-role msgs
ok = sanity_check_messages(messages) # True if last non-system msg is user
if not ok:
messages.append({"role": "user", "content": "continue"})
return messages Type guard
def messages_alternate(messages) -> bool:
last = None
for m in messages:
r = m.get("role")
if r == "system":
continue
if r == last:
return False
last = r
return True Try / catch
try:
sanity_check_messages(messages)
except ValueError as e:
if "don't properly alternate" in str(e):
messages = ensure_alternating_roles(messages) # auto-repair and retry
sanity_check_messages(messages)
else:
raise Prevention
- Always run ensure_alternating_roles before sending to strict-alternation models (e.g. DeepSeek R1).
- Never concatenate two same-role messages when building history manually — merge their content instead.
- The error dump prints the full transcript; the first duplicated pair is your construction bug.
When it happens
Trigger: Sending a completion where messages = [user, user, ...] or [assistant, assistant, ...] consecutively (system msgs ignored), e.g. after manually constructing history, merging two user edits into one turn, or a bug in chat-history replay that emits back-to-back same-role messages. Under send_completion it fires when the env var AIDER_SANITY_CHECK_TURNS is set; role alternation matters unconditionally for strict-alternation models.
Common situations: Building custom message histories for aider's model layer; summarization producing a user summary message concatenated after an existing user message; adapting transcripts from other tools that allow consecutive user turns.
Related errors
- summarizer unexpectedly failed for all models
- Invalid line_endings value: {line_endings}. Must be one of:
- Unsupported audio format: {audio_format}
- Unknown edit format {edit_format}. Valid formats are: {', '.
- No data found in LLM response!
AI-assisted analysis of Aider-AI/aider@5dc9490bb3 (2026-08-15).
Data as JSON: /api/errors/7e3e80e4593ef69c.
Report an issue: GitHub.