abi/screenshot-to-code · error · ValueError
Update history must include at least one user message
Error message
Update history must include at least one user message
What it means
Raised in build_update_prompt_from_history when no message in the history list has role "user". Update-from-history prompts are anchored on the first user message (found via a linear scan); a history containing only assistant/system messages, or an empty list, leaves no anchor and the builder refuses to construct the prompt.
Source
Thrown at backend/prompts/update/from_history.py:23
from prompts import system_prompt
from prompts.design_system import build_design_system_prompt_block
from prompts.policies import build_selected_stack_policy, build_user_image_policy
from prompts.prompt_types import PromptHistoryMessage, Stack
from prompts.message_builder import Prompt, build_history_message
def build_update_prompt_from_history(
stack: Stack,
history: list[PromptHistoryMessage],
image_generation_enabled: bool,
design_system: str | None = None,
) -> Prompt:
first_user_index = next(
(index for index, item in enumerate(history) if item["role"] == "user"),
-1,
)
if first_user_index == -1:
raise ValueError("Update history must include at least one user message")
prompt_messages: Prompt = [
cast(
ChatCompletionMessageParam,
{
"role": "system",
"content": system_prompt.SYSTEM_PROMPT,
},
)
]
selected_stack = build_selected_stack_policy(stack)
image_policy = build_user_image_policy(image_generation_enabled)
design_system_block = build_design_system_prompt_block(design_system)
for index, item in enumerate(history):
if index == first_user_index:
stack_prefix_parts = [selected_stack, image_policy]
if design_system_block:
stack_prefix_parts.append(design_system_block.strip())View on GitHub (pinned to d026163f58)
Solutions
- Ensure the history array includes the original user message(s) with role exactly "user" (lowercase).
- Fix the upstream serializer that produced assistant-only history.
- If no user turn exists, use the file-snapshot strategy instead: pass fileState.content so plan.py picks "update_from_file_snapshot".
- Add a route-level check: reject update requests whose history has no user message with a 400.
Example fix
# before
history = [{"role": "assistant", "content": "..."}]
# after
history = [
{"role": "user", "content": "make a landing page"},
{"role": "assistant", "content": "..."},
] Defensive patterns
Strategy: validation
Validate before calling
def history_has_user_message(history: list[dict]) -> bool:
return any(item.get("role") == "user" for item in history) Type guard
def is_valid_update_history(history: list[dict]) -> bool:
"""True when update-from-history can anchor on a user message."""
return any(
isinstance(item, dict) and item.get("role") == "user" for item in history
) Try / catch
try:
prompt = build_update_prompt_from_history(stack, history, image_gen_enabled)
except ValueError as e:
if "at least one user message" in str(e):
return error_response("Update history is missing the original user message", 400)
raise Prevention
- Always send the full conversation including the first user turn on updates
- Use exactly lowercase role strings ('user'/'assistant') in serialized history
- If history was lost, fall back to fileState.content so plan.py picks the snapshot strategy
When it happens
Trigger: The plan strategy "update_from_history" was selected (history length > 0 per plan.py) but every item's role is "assistant" — e.g. a truncated or filtered history that dropped user turns; or roles serialized with different casing ("User") by a client; or history entries missing the role key entirely (item["role"] would KeyError first, so in practice it's all-assistant histories).
Common situations: History persisted/relayed from a client that strips user messages for size; role casing or naming drift between frontend and backend ("User" vs "user"); hand-built history arrays for API testing containing only model responses.
Related errors
- Update requests require history or fileState.content
- Video mode requires a video to be provided
- Unsupported input mode: {input_mode}
- --iou-threshold must be between 0 and 1
- No stack was provided
AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14).
Data as JSON: /api/errors/e74a0276b7b01a51.
Report an issue: GitHub.