Fix: gracefully error out for unsupported images (#7478)

Fix for #7459 
## What
Since codex errors out for unsupported images, stop attempting to
base64/attach them and instead emit a clear placeholder when the file
isn’t a supported image MIME.

## Why
Local uploads for unsupported formats (e.g., SVG/GIF/etc.) were
dead-ending after decode failures because of the 400 retry loop. Users
now get an explicit “cannot attach … unsupported image format …”
response.

## How
Replace the fallback read/encode path with MIME detection that bails out
for non-image or unsupported image types, returning a consistent
placeholder. Unreadable and invalid images still produce their existing
error placeholders.
This commit is contained in:
Job Chong authored and GitHub committed 2025-12-09 10:28:41 -08:00
1 parent 9df70a0772
commit ac3237721e
3 files changed
+55 -33

No files matched your search

-1
View File
@@ -1491,7 +1491,6 @@ name = "codex-protocol"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64",
"codex-git", "codex-git",
"codex-utils-image", "codex-utils-image",
"icu_decimal", "icu_decimal",
-1
View File
@@ -14,7 +14,6 @@ workspace = true
[dependencies] [dependencies]
codex-git = { workspace = true } codex-git = { workspace = true }
base64 = { workspace = true }
codex-utils-image = { workspace = true } codex-utils-image = { workspace = true }
icu_decimal = { workspace = true } icu_decimal = { workspace = true }
icu_locale_core = { workspace = true } icu_locale_core = { workspace = true }
+55 -31
View File
@@ -1,6 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use base64::Engine;
use codex_utils_image::load_and_resize_to_fit; use codex_utils_image::load_and_resize_to_fit;
use mcp_types::CallToolResult; use mcp_types::CallToolResult;
use mcp_types::ContentBlock; use mcp_types::ContentBlock;
@@ -175,6 +174,16 @@ fn invalid_image_error_placeholder(
} }
} }
fn unsupported_image_error_placeholder(path: &std::path::Path, mime: &str) -> ContentItem {
ContentItem::InputText {
text: format!(
"Codex cannot attach image at `{}`: unsupported image format `{}`.",
path.display(),
mime
),
}
}
impl From<ResponseInputItem> for ResponseItem { impl From<ResponseInputItem> for ResponseItem {
fn from(item: ResponseInputItem) -> Self { fn from(item: ResponseInputItem) -> Self {
match item { match item {
@@ -285,37 +294,20 @@ impl From<Vec<UserInput>> for ResponseInputItem {
} else if err.is_invalid_image() { } else if err.is_invalid_image() {
invalid_image_error_placeholder(&path, &err) invalid_image_error_placeholder(&path, &err)
} else { } else {
match std::fs::read(&path) { let Some(mime_guess) = mime_guess::from_path(&path).first() else {
Ok(bytes) => { return local_image_error_placeholder(
let Some(mime_guess) = mime_guess::from_path(&path).first() &path,
else { "unsupported MIME type (unknown)",
return local_image_error_placeholder( );
&path, };
"unsupported MIME type (unknown)", let mime = mime_guess.essence_str().to_owned();
); if !mime.starts_with("image/") {
}; return local_image_error_placeholder(
let mime = mime_guess.essence_str().to_owned(); &path,
if !mime.starts_with("image/") { format!("unsupported MIME type `{mime}`"),
return local_image_error_placeholder( );
&path,
format!("unsupported MIME type `{mime}`"),
);
}
let encoded =
base64::engine::general_purpose::STANDARD.encode(bytes);
ContentItem::InputImage {
image_url: format!("data:{mime};base64,{encoded}"),
}
}
Err(read_err) => {
tracing::warn!(
"Skipping image {} – could not read file: {}",
path.display(),
read_err
);
local_image_error_placeholder(&path, &read_err)
}
} }
unsupported_image_error_placeholder(&path, &mime)
} }
} }
}, },
@@ -823,4 +815,36 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn local_image_unsupported_image_format_adds_placeholder() -> Result<()> {
let dir = tempdir()?;
let svg_path = dir.path().join("example.svg");
std::fs::write(
&svg_path,
br#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>"#,
)?;
let item = ResponseInputItem::from(vec![UserInput::LocalImage {
path: svg_path.clone(),
}]);
match item {
ResponseInputItem::Message { content, .. } => {
assert_eq!(content.len(), 1);
let expected = format!(
"Codex cannot attach image at `{}`: unsupported image format `image/svg+xml`.",
svg_path.display()
);
match &content[0] {
ContentItem::InputText { text } => assert_eq!(text, &expected),
other => panic!("expected placeholder text but found {other:?}"),
}
}
other => panic!("expected message response but got {other:?}"),
}
Ok(())
}
} }