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

  1. Reduce the image size (resize/compress) before attaching so its base64 data URL fits within MAX_IMAGE_BYTES
  2. Raise MAX_IMAGE_BYTES in image_attach.rs if the deployment genuinely needs larger attachments
  3. 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

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


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)