{"record":{"id":"8622d1e68dc01eda","repo":"Hmbown/CodeWhale","slug":"persisted-image-input-must-contain-an-image","errorCode":null,"errorMessage":"persisted image input must contain an image","messagePattern":"persisted image input must contain an image","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/image_attach.rs","lineNumber":202,"sourceCode":"        }\n    }\n    prepare_stored_images(&images)?;\n    Ok(images)\n}\n\n/// Validate new image-bearing durable records without rewriting their block order.\n/// Legacy schema 2 history continues to use its original interpretation.\npub(crate) fn validate_stored_image_content(blocks: &[ContentBlock]) -> Result<()> {\n    if blocks.iter().any(|block| {\n        !matches!(\n            block,\n            ContentBlock::Text { .. } | ContentBlock::ImageUrl { .. }\n        )\n    }) {\n        bail!(\"invalid persisted user image content kind\");\n    }\n    if runtime_images_from_blocks(blocks)?.is_empty() {\n        bail!(\"persisted image input must contain an image\");\n    }\n    Ok(())\n}\n\n/// Why a file could not be attached as an image.\n///\n/// Every variant renders to a sentence naming the file and the reason. These\n/// strings reach both the user (as a command error) and the model (as an\n/// in-band notice), so they say what to do next rather than only what failed.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum ImageAttachError {\n    /// The file could not be read at all.\n    Unreadable { path: String, reason: String },\n    /// The file is zero bytes.\n    Empty { path: String },\n    /// Over [`MAX_IMAGE_BYTES`].\n    TooLarge { path: String, bytes: usize },\n    /// Magic bytes identify a format no provider in the set accepts.","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/image_attach.rs#L184-L220","documentation":"Thrown by `validate_stored_image_content` when the persisted block list passes the kind check but contains no ImageUrl block at all — i.e. the runtime image extraction yields an empty Vec. A message stored as an 'image input' must contain at least one image, so a text-only record is rejected.","triggerScenarios":"Calling `validate_stored_image_content` with blocks containing only ContentBlock::Text entries (or ImageUrl entries that were never present), so `runtime_images_from_blocks(blocks)?` returns an empty vector.","commonSituations":"Persisting a user message that had its image stripped (e.g. during context trimming) but is still validated as an image input; a migration or hand-edit that dropped the image block while keeping the validation call.","solutions":["Include at least one ImageUrl content block in the persisted message before validating","Re-classify the message as a plain text message instead of an image input","Fix the persistence/trimming path so image blocks are not dropped from image messages"],"exampleFix":"// before: text-only blocks validated as image input\nvalidate_stored_image_content(&[ContentBlock::Text{text: \"hi\".into()}])?;\n// after: include an image block\nvalidate_stored_image_content(&[ContentBlock::Text{text: \"hi\".into()}, ContentBlock::ImageUrl{image_url}])?;","handlingStrategy":"validation","validationCode":"fn has_image(blocks: &[ContentBlock]) -> bool {\n    blocks.iter().any(|b| matches!(b, ContentBlock::ImageUrl { .. }))\n}","typeGuard":"fn is_image_url(b: &ContentBlock) -> bool {\n    matches!(b, ContentBlock::ImageUrl { .. })\n}","tryCatchPattern":"match validate_stored_image_content(blocks) {\n    Ok(()) => /* proceed */,\n    Err(e) if e.to_string().contains(\"must contain an image\") => /* reclassify as plain text message */,\n    Err(e) => return Err(e),\n}","preventionTips":["Ensure context-trimming paths never strip ImageUrl blocks from image messages","Validate a message contains an image before persisting it as an image input","Classify text-only messages as plain user messages, not image inputs"],"tags":["validation","image","empty","persistence"],"backgroundTag":"empty-required-field","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}