Hmbown/CodeWhale · error
persisted image input must contain an image
Error message
persisted image input must contain an image
What it means
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.
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
Example fix
// before: text-only blocks validated as image input
validate_stored_image_content(&[ContentBlock::Text{text: "hi".into()}])?;
// after: include an image block
validate_stored_image_content(&[ContentBlock::Text{text: "hi".into()}, ContentBlock::ImageUrl{image_url}])?; Defensive patterns
Strategy: validation
Validate before calling
fn has_image(blocks: &[ContentBlock]) -> bool {
blocks.iter().any(|b| matches!(b, ContentBlock::ImageUrl { .. }))
} Type guard
fn is_image_url(b: &ContentBlock) -> bool {
matches!(b, ContentBlock::ImageUrl { .. })
} Try / catch
match validate_stored_image_content(blocks) {
Ok(()) => /* proceed */,
Err(e) if e.to_string().contains("must contain an image") => /* reclassify as plain text message */,
Err(e) => return Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- invalid persisted user image content kind
- image has invalid base64
- image MIME does not match its content
- images exceed the 5 MiB total limit
- images exceed the attachment limit
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8622d1e68dc01eda.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/image_attach.rs:202
}
}
prepare_stored_images(&images)?;
Ok(images)
}
/// Validate new image-bearing durable records without rewriting their block order.
/// Legacy schema 2 history continues to use its original interpretation.
pub(crate) fn validate_stored_image_content(blocks: &[ContentBlock]) -> Result<()> {
if blocks.iter().any(|block| {
!matches!(
block,
ContentBlock::Text { .. } | ContentBlock::ImageUrl { .. }
)
}) {
bail!("invalid persisted user image content kind");
}
if runtime_images_from_blocks(blocks)?.is_empty() {
bail!("persisted image input must contain an image");
}
Ok(())
}
/// Why a file could not be attached as an image.
///
/// Every variant renders to a sentence naming the file and the reason. These
/// strings reach both the user (as a command error) and the model (as an
/// in-band notice), so they say what to do next rather than only what failed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImageAttachError {
/// The file could not be read at all.
Unreadable { path: String, reason: String },
/// The file is zero bytes.
Empty { path: String },
/// Over [`MAX_IMAGE_BYTES`].
TooLarge { path: String, bytes: usize },
/// Magic bytes identify a format no provider in the set accepts.View on GitHub (pinned to 73e0f67d83)