Hmbown/CodeWhale · error
stored image requires canonical inline content
Error message
stored image requires canonical inline content
What it means
A stored ContentBlock::ImageUrl does not carry a parseable canonical inline data URL. parse_data_url returned None, so the block is either a remote http(s) URL, or a data URL that is malformed (missing the `data:` prefix, missing `;base64,`, or non-base64 media).
Solutions
- Convert the image to inline canonical form: fetch the bytes, base64-encode them, and store `data:<mime>;base64,<payload>`.
- Check that the stored URL is a data URL with an explicit mime type and `;base64,` marker, not a plain http link.
- Remove the block from history if the image content is no longer needed for the conversation.
Example fix
// before
{ "type": "image_url", "image_url": { "url": "https://example.com/img.png" } }
// after
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } } Defensive patterns
Strategy: validation
Validate before calling
fn is_canonical_inline_data_url(url: &str) -> bool {
let Some(rest) = url.strip_prefix("data:") else { return false };
let Some((mime, payload)) = rest.split_once(";base64,") else { return false };
!mime.is_empty() && !payload.is_empty()
} Try / catch
// Rust
for block in blocks {
if let ContentBlock::ImageUrl { image_url } = block {
if !image_url.url.starts_with("data:") {
// remote URL: fetch + inline before admitting
image_url.url = inline_as_data_url(&fetch(image_url.url)?);
}
}
} Prevention
- Never persist remote http(s) image URLs in stored history; always inline as data URLs.
- Run validate_stored_image_content on session logs after config or provider migrations.
- Generate data URLs with an explicit mime type and `;base64,` marker.
When it happens
Trigger: runtime_images_from_blocks (called by validate_stored_image_content) encounters an ImageUrl block whose image_url.url is not a well-formed `data:<mime>;base64,<payload>` URL — typically an http(s) link or an empty/mangled data URL.
Common situations: Older or third-party session logs that stored remote image URLs instead of inline data; hand-edited history files; providers that emit image URLs rather than base64 content blocks.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- image has invalid base64
- image MIME does not match its content
- images exceed the 5 MiB total limit
- images exceed the attachment limit
- invalid persisted user image content kind
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/dfd72c31c19637f5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/image_attach.rs:179
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 {
if image_url.url.len() > MAX_IMAGE_BYTES.div_ceil(3) * 4 + 32 {
bail!("stored image exceeds the attachment limit");
}
let (mime, data) = parse_data_url(&image_url.url)
.ok_or_else(|| anyhow::anyhow!("stored image requires canonical inline content"))?;
images.push(RuntimeImageInput {
mime: mime.to_string(),
data_base64: data.to_string(),
});
}
}
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 { .. }
)View on GitHub (pinned to 73e0f67d83)