Malformed image (bad PNG bytes / paste / 404'd URL) permanently bricks the session — `API Error: 400 Could not process image` on every turn
Pasting / reading a malformed PNG bricks the session permanently with API Error: 400 Could not process image
Summary
When Claude Code attaches an image whose bytes the API cannot decode (corrupted PNG, file with .png extension that's actually HTML, base64 paste with a bad header, etc.), the API responds with HTTP 400 Could not process image. Claude Code does not remove the offending content from the conversation, so every subsequent prompt — including a /exit followed by --resume — re-sends the same poisoned context and fails the same way. The session becomes permanently unusable.
Repro
- Save a non-image file with a
.pngextension (e.g. an HTML error page, or some random base64 the user pastes from a webpage):
``bash``
echo '<!DOCTYPE html><html>oops</html>' > /tmp/fake.png
- In Claude Code, ask the model to look at
/tmp/fake.png. The model usesRead, which auto-attaches the file as an image content block based on extension. - Next API call fails immediately:
````
⎿ API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"}}
- Type any message. Same 400.
/exit, thenclaude --resume <that session>. Same 400 — context still contains the bad image.- Session is bricked. The only escape is to manually edit the JSONL on disk and replace the bad image block.
Same outcome if the user pastes a base64 PNG into the prompt where the base64 is truncated, has a non-PNG header, or is otherwise malformed.
Root cause (two compounding issues)
- No validation before sending. The
Readtool (and prompt paste) accepts content withimage/pngmedia type without checking the magic bytes. A 2860-byte file starting with<!DOCTYPis happily encoded and shipped asimage/png. - No recovery from a 400. When the API returns
invalid_request_error: Could not process image, Claude Code keeps the content block in conversation state and resends it on every subsequent turn. There is no "this image is poison, drop it" path.
Either fix alone would prevent permanent bricking. Both should be fixed.
Suggested fixes
- Validate magic bytes before constructing an image content block. Reject (or downgrade to a text attachment that says "couldn't read as image, here's a hex preview") when the header doesn't match the declared media type. Same check for prompt paste of a base64 string.
- On
400 Could not process image, surface a clear error to the user and offer to drop the most recently added image content block(s) from the context, then retry. Currently the user has zero recourse from the TUI.
Real-world example
Today I asked Claude Code to curl a few logo URLs and check them. One returned an HTML error page that got saved as g-test.png. The model's next Read of that path put the HTML bytes into an image/png content block. The session bricked. After /exit and --resume, the API kept failing on the same image. I had to:
- Locate the session file at
~/.claude/projects/<slug>/<uuid>.jsonl - Parse it, find the one bad
tool_result→image/pngblock whose base64 decoded to<!DOCTYPE html> - Replace it with a text placeholder
- Resume
That's a lot of manual recovery for a "the model fetched a URL that 404'd" scenario.
Environment
- Claude Code 2.1.119 / 2.1.120 (also reproducible on prior versions, this is not a regression)
- Linux, Bun-bundled native binary
- API endpoint: production Anthropic API
Impact
- Sessions with hours of context can be killed by a single corrupted image (URL 404 returning HTML, partial paste, broken file).
--resumedoesn't help — the poison is in the persisted JSONL.- The error message is a generic 400; nothing in the TUI tells the user which image is bad or how to remove it. Users with no JSONL spelunking skills lose the entire session.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗