{"record":{"id":"d8634b28f56e8b7f","repo":"Hmbown/CodeWhale","slug":"invalid-tool-image-evidence","errorCode":null,"errorMessage":"invalid tool image evidence","messagePattern":"invalid tool image evidence","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/core/engine/tool_media.rs","lineNumber":73,"sourceCode":"    })\n    .await\n    .unwrap_or_else(|_| {\n        let mut rich = RichToolResult::plain(fallback);\n        rich.result\n            .content\n            .push_str(\"\\n[Tool image omitted: image preparation failed.]\");\n        rich\n    })\n}\n\nfn publish_image(\n    block: &ToolResultContentBlock,\n    session_id: &str,\n    call_id: &str,\n    tool_name: &str,\n) -> io::Result<Value> {\n    let ToolResultContentBlock::Image { mime_type, data } = block;\n    let invalid = || io::Error::new(io::ErrorKind::InvalidData, \"invalid tool image evidence\");\n    let bytes = STANDARD.decode(data).map_err(|_| invalid())?;\n    // The caller passed the shared rich-image validator; retain the shared decode\n    // guard when deriving dimensions rather than trusting metadata or headers.\n    let (_, width, height) =\n        crate::image_attach::decode_and_guard_image(&bytes).map_err(|_| invalid())?;\n    let identity = serde_json::to_vec(&(session_id, call_id, 0u8)).map_err(|_| invalid())?;\n    let handle = format!(\"art_image_{}\", crate::hashing::sha256_hex(&identity));\n    let storage_path =\n        PathBuf::from(crate::artifacts::ARTIFACTS_DIR_NAME).join(format!(\"{handle}.image\"));\n    let digest = crate::hashing::sha256_hex(&bytes);\n    let now = unix_millis_now();\n    let proposed = EvidenceArtifact {\n        handle: handle.clone(),\n        digest: digest.clone(),\n        size_bytes: bytes.len() as u64,\n        content_type: mime_type.clone(),\n        tool_name: tool_name.to_owned(),\n        call_id: call_id.to_owned(),","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/core/engine/tool_media.rs#L55-L91","documentation":"`publish_image` turns a tool-result image block into persisted rich media evidence. It base64-decodes the block's data and then re-validates the bytes with the shared `decode_and_guard_image` validator; any failure to decode, decode-guard, or even serialize the identity tuple raises this generic `InvalidData` error rather than trusting metadata or headers.","triggerScenarios":"`project` -> `publish_image` with a `ToolResultContentBlock::Image` whose `data` is not valid base64, whose decoded bytes are not a supported/valid image (per the shared rich-image validator), or with corrupted internal serialization input.","commonSituations":"A tool (or a provider relaying tool output) emitted a malformed or truncated image payload; the base64 string contains invalid characters or wrong padding; the image is in an unsupported codec or fails dimension/format guards.","solutions":["Fix the tool/provider so the image block contains valid base64 of a supported image format (check for truncation, whitespace, or wrong encoding).","Re-run the tool call that produced the image to regenerate a clean payload.","Inspect the payload: `base64 -d < data.b64 > img.bin && file img.bin` to see whether the bytes form a real image.","If your pipeline transforms tool output, verify it does not re-encode or corrupt the base64 data before it reaches the engine."],"exampleFix":"// before: trusting/forwarding raw tool payload\ndata: block.data.clone(),\n// after (caller side): validate before publishing\nlet bytes = STANDARD.decode(&payload).map_err(|_| anyhow::anyhow!(\"invalid base64\"))?;\nimage::load_from_memory(&bytes)?; // ensure decodable before handing to publish_image","handlingStrategy":"validation","validationCode":"use base64::Engine;\nfn tool_image_bytes_ok(data: &str) -> Option<Vec<u8>> {\n    let bytes = base64::engine::general_purpose::STANDARD.decode(data).ok()?;\n    image::load_from_memory(&bytes).ok()?;\n    Some(bytes)\n}","typeGuard":"fn is_valid_image_block(block: &ToolResultContentBlock) -> bool {\n    matches!(block, ToolResultContentBlock::Image { data, .. })\n        && tool_image_bytes_ok(data).is_some()\n}","tryCatchPattern":"match publish_image(block, session_id, call_id, tool_name) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        log::warn!(\"tool returned invalid image evidence for {call_id}; skipping media render\");\n        // degrade gracefully: render the tool result without the image\n    }\n    other => other?,\n}","preventionTips":["Validate tool outputs at the boundary: base64-decode and image-decode before trusting media blocks.","Keep tool image payloads unmodified end-to-end (no re-encoding in proxies or relays).","Prefer standard base64 with correct padding from tools emitting images.","Add a fixture test per supported image format your tools emit."],"tags":["validation","base64","image","tool-output"],"backgroundTag":"schema-validation-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}