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

  1. Run ensure_alternating_roles(messages) (same module) before sending — it inserts empty opposite-role messages to repair the list.
  2. 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.
  3. 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

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


AI-assisted analysis of Aider-AI/aider@5dc9490bb3 (2026-08-15). Data as JSON: /api/errors/7e3e80e4593ef69c. Report an issue: GitHub.