zylon-ai/private-gpt · error · ValueError
User block {i} does not start with a user message: {block[0]
Error message
User block {i} does not start with a user message: {block[0]} What it means
User-block structure validation: each block produced by get_user_blocks must start with a role=='user' message. Because blocks are defined as starting at user messages, a block whose first message is not 'user' means the splitting invariant broke (or the history was mutated between split and validation).
Source
Thrown at private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py:135
def _assert_user_blocks(chat_history: list[ChatMessage], strict: bool) -> None:
"""A user block should follow a regex pattern.
Strict mode: (user, (assistant, tool)*, assistant)
Non-strict mode: (user, (assistant, tool)*, assistant?)
"""
if not chat_history:
return
matrix = get_user_blocks(chat_history)
for i, block in enumerate(matrix):
if not block:
raise ValueError(f"User block {i} is empty.")
# Validate the first and last messages in the block
if block[0].role != "user":
raise ValueError(
f"User block {i} does not start with a user message: {block[0]}"
)
if len(block) == 1:
# No more validation needed for a single user message block
continue
# Validate the last message in the block
if strict and block[-1].role != "assistant":
raise ValueError(
f"User block {i} does not end with an assistant message: {block[-1]}"
)
elif not strict and block[-1].role not in ["assistant", "tool"]:
raise ValueError(
f"User block {i} does not end with an assistant or tool message: {block[-1]}"
)
# Validate the tool messagesView on GitHub (pinned to 4a030776a3)
Solutions
- Ensure conversations passed to repair_* start with a user message after system messages
- Prepend a synthetic user message for assistant-first histories
- Fix upstream code that injects assistant/tool messages before the first user turn
Example fix
# before
repaired = repair_with_tools(conversation_messages)
# after
if conversation_messages and conversation_messages[0].role != "user":
conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages
repaired = repair_with_tools(conversation_messages) Defensive patterns
Strategy: validation
Validate before calling
if conversation_messages and conversation_messages[0].role != "user":
conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages Type guard
def starts_with_user(history: list[ChatMessage]) -> bool:
return bool(history) and history[0].role == "user" Try / catch
try:
repaired = repair_with_tools(conversation_messages)
except ValueError as e:
if "does not start with a user message" in str(e):
conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages
repaired = repair_with_tools(conversation_messages)
else:
raise Prevention
- Normalize histories to user-first before repair
- Reject assistant-first transcripts at ingestion or prepend a synthetic user turn
- Log the offending block index to locate the producer bug quickly
When it happens
Trigger: Calling repair_without_tools()/repair_with_tools() on a conversation whose first non-system message is not a user message (e.g. assistant-first conversation), so the leading chunk fails the block[0].role != 'user' check.
Common situations: Histories that begin with an assistant greeting; a tool message appearing before any user message; custom history loaders that reorder messages.
Related errors
- User block {i} does not end with an assistant message: {bloc
- User block {i} does not end with an assistant or tool messag
- Tool message {j} in user block {i} does not follow an assist
- Message {j} in user block {i} is neither an assistant nor a
- User block {i} is empty.
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/14c68db95056d92a.
Report an issue: GitHub.