{"record":{"id":"e74a0276b7b01a51","repo":"abi/screenshot-to-code","slug":"update-history-must-include-at-least-one-user-mess","errorCode":null,"errorMessage":"Update history must include at least one user message","messagePattern":"Update history must include at least one user message","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/prompts/update/from_history.py","lineNumber":23,"sourceCode":"from prompts import system_prompt\nfrom prompts.design_system import build_design_system_prompt_block\nfrom prompts.policies import build_selected_stack_policy, build_user_image_policy\nfrom prompts.prompt_types import PromptHistoryMessage, Stack\nfrom prompts.message_builder import Prompt, build_history_message\n\n\ndef build_update_prompt_from_history(\n    stack: Stack,\n    history: list[PromptHistoryMessage],\n    image_generation_enabled: bool,\n    design_system: str | None = None,\n) -> Prompt:\n    first_user_index = next(\n        (index for index, item in enumerate(history) if item[\"role\"] == \"user\"),\n        -1,\n    )\n    if first_user_index == -1:\n        raise ValueError(\"Update history must include at least one user message\")\n\n    prompt_messages: Prompt = [\n        cast(\n            ChatCompletionMessageParam,\n            {\n                \"role\": \"system\",\n                \"content\": system_prompt.SYSTEM_PROMPT,\n            },\n        )\n    ]\n    selected_stack = build_selected_stack_policy(stack)\n    image_policy = build_user_image_policy(image_generation_enabled)\n    design_system_block = build_design_system_prompt_block(design_system)\n    for index, item in enumerate(history):\n        if index == first_user_index:\n            stack_prefix_parts = [selected_stack, image_policy]\n            if design_system_block:\n                stack_prefix_parts.append(design_system_block.strip())","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/prompts/update/from_history.py#L5-L41","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before\nhistory = [{\"role\": \"assistant\", \"content\": \"...\"}]\n\n# after\nhistory = [\n    {\"role\": \"user\", \"content\": \"make a landing page\"},\n    {\"role\": \"assistant\", \"content\": \"...\"},\n]","handlingStrategy":"validation","validationCode":"def history_has_user_message(history: list[dict]) -> bool:\n    return any(item.get(\"role\") == \"user\" for item in history)","typeGuard":"def is_valid_update_history(history: list[dict]) -> bool:\n    \"\"\"True when update-from-history can anchor on a user message.\"\"\"\n    return any(\n        isinstance(item, dict) and item.get(\"role\") == \"user\" for item in history\n    )","tryCatchPattern":"try:\n    prompt = build_update_prompt_from_history(stack, history, image_gen_enabled)\nexcept ValueError as e:\n    if \"at least one user message\" in str(e):\n        return error_response(\"Update history is missing the original user message\", 400)\n    raise","preventionTips":["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"],"tags":["prompts","validation","update","chat-history"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}