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

  1. Ensure the history array includes the original user message(s) with role exactly "user" (lowercase).
  2. Fix the upstream serializer that produced assistant-only history.
  3. If no user turn exists, use the file-snapshot strategy instead: pass fileState.content so plan.py picks "update_from_file_snapshot".
  4. 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

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


AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14). Data as JSON: /api/errors/e74a0276b7b01a51. Report an issue: GitHub.