Hmbown/CodeWhale · error

image MIME does not match its content

Error message

image {} MIME does not match its content

What it means

The image's declared MIME type does not match the media type detected from its actual bytes by encode_image_bytes (which sniffs content, e.g. via magic bytes). prepare_images_with_limit rejects the mismatch so the provider-neutral history never carries a mislabeled content block, since downstream consumers trust the declared media_type.

Solutions

  1. Derive the MIME type from the image bytes (magic-number sniffing) instead of the filename or client header.
  2. Fix the stored record's mime field to match the real content format.
  3. Re-encode the image into the format its declared MIME claims if that format is required.
  4. At ingestion, reject or normalize mismatched MIME before the image reaches this validation path.

Example fix

// before
RuntimeImageInput { mime: "image/png", data_base64: jpeg_b64, .. }
// after
RuntimeImageInput { mime: "image/jpeg", data_base64: jpeg_b64, .. }
Defensive patterns

Strategy: validation

Validate before calling

fn mime_matches(data_base64: &str, claimed: &str) -> bool {
    STANDARD.decode(data_base64)
        .ok()
        .and_then(|b| infer_image_mime(&b))
        .map_or(false, |detected| detected == claimed)
}

Type guard

fn sniffed_mime(bytes: &[u8]) -> Option<&'static str> {
    match bytes.first()? {
        0x89 if bytes.start_with(b"\x89PNG") => Some("image/png"),
        0xFF if bytes.start_with(&[0xFF, 0xD8]) => Some("image/jpeg"),
        b'G' if bytes.start_with(b"GIF8") => Some("image/gif"),
        _ => None,
    }
}

Try / catch

match prepare_runtime_images(&images) {
    Err(e) if e.to_string().contains("MIME does not match") => {
        eprintln!("derive mime from magic bytes, not the filename"); }
    other => other?,
}

Prevention

When it happens

Trigger: prepare_runtime_images or prepare_stored_images receives a RuntimeImageInput whose mime field says e.g. image/png but whose bytes are actually JPEG/GIF/WebP (or vice versa) — detected when image.mime != attached.media_type after content sniffing.

Common situations: Renaming a .jpg to .png and setting mime from the extension; copy-paste code that hardcodes "image/png"; HTTP uploads where Content-Type came from the browser's guess; stored records written by an older path that trusted client-supplied MIME.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/a89fb8188fdfb78d. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/image_attach.rs:156

                );
            }
            let bytes = STANDARD
                .decode(&image.data_base64)
                .map_err(|_| anyhow::anyhow!("image {} has invalid base64", index + 1))?;
            if bytes.len() > per_image_limit {
                bail!(
                    "image {} exceeds the {} MiB limit",
                    index + 1,
                    per_image_limit / (1024 * 1024)
                );
            }
            total = total.saturating_add(bytes.len());
            if total_limit.is_some_and(|limit| total > limit) {
                bail!("images exceed the 5 MiB total limit");
            }
            let attached = encode_image_bytes(&bytes, &format!("image {}", index + 1))?;
            if image.mime != attached.media_type {
                bail!("image {} MIME does not match its content", index + 1);
            }
            decode_and_guard_image(&bytes)?;
            // Standard padded base64 is the one replay representation.
            if STANDARD.encode(&bytes) != image.data_base64 {
                bail!("image {} base64 is not canonical", index + 1);
            }
            Ok(attached.content_block())
        })
        .collect()
}

/// Reuse durable canonical bytes for retry, never reread a path or URL.
pub(crate) fn runtime_images_from_blocks(
    blocks: &[ContentBlock],
) -> Result<Vec<RuntimeImageInput>> {
    let mut images = Vec::new();
    for block in blocks {
        if let ContentBlock::ImageUrl { image_url } = block {

View on GitHub (pinned to 73e0f67d83)