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

  1. Include at least one ImageUrl content block in the persisted message before validating
  2. Re-classify the message as a plain text message instead of an image input
  3. 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

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


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)