Hmbown/CodeWhale · error
stored image exceeds the attachment limit
Error message
stored image exceeds the attachment limit
What it means
This error is thrown by `runtime_images_from_blocks` in crates/tui/src/image_attach.rs when converting persisted ImageUrl content blocks into runtime image inputs. It guards the total stored payload size: the base64 data URL string must fit within MAX_IMAGE_BYTES (converted via div_ceil(3)*4 + 32 to account for base64 expansion). The library throws it to prevent oversized stored attachments from being re-loaded into a runtime request.
Solutions
- Reduce the image size (resize/compress) before attaching so its base64 data URL fits within MAX_IMAGE_BYTES
- Raise MAX_IMAGE_BYTES in image_attach.rs if the deployment genuinely needs larger attachments
- Remove the oversized image block from the persisted session history before re-validating
Example fix
// before: huge image stored as data URL exceeds limit
ContentBlock::ImageUrl { image_url: ImageUrl { url: "data:image/png;base64,<8MB-of-base64>" } }
// after: downscale/compress first so the data URL fits
ContentBlock::ImageUrl { image_url: ImageUrl { url: format!("data:{};base64,{}", mime, small_base64) } } Defensive patterns
Strategy: validation
Validate before calling
const MAX_IMAGE_BYTES: usize = /* from image_attach.rs */;
fn fits_limit(url: &str) -> bool {
url.len() <= MAX_IMAGE_BYTES.div_ceil(3) * 4 + 32
}
// check fits_limit(&image_url.url) before pushing the block Try / catch
match runtime_images_from_blocks(blocks) {
Ok(images) => /* use images */,
Err(e) if e.to_string().contains("exceeds the attachment limit") => /* resize/compress or drop the image */,
Err(e) => return Err(e),
} Prevention
- Compress/resize images before attaching so base64 size stays well under MAX_IMAGE_BYTES
- Check the data URL length client-side before persisting a session
- Keep MAX_IMAGE_BYTES stable or migrate stored records when changing it
When it happens
Trigger: Calling `runtime_images_from_blocks` (directly or via `validate_stored_image_content`) with a ContentBlock::ImageUrl whose image_url.url string length exceeds MAX_IMAGE_BYTES.div_ceil(3) * 4 + 32 characters.
Common situations: Resuming a session whose stored history contains an image attachment larger than the configured MAX_IMAGE_BYTES limit; a user hand-edited or migrated persisted session JSON with a huge base64 data URL; MAX_IMAGE_BYTES was lowered after the image was originally stored.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- Fleet artifact exceeds the 16 MiB limit
- 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/566e069a161d4581.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/image_attach.rs:176
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 {
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!(View on GitHub (pinned to 73e0f67d83)