Read tool sets media_type from file extension, poisoning session if SVG has .png extension
Summary
The Read tool determines the image media_type from the file extension rather than sniffing magic bytes. When a file with a misleading extension (e.g. an SVG saved as foo.png) is read, the resulting tool_result carries media_type: image/png with <svg> bytes. Anthropic's Messages API rejects it with 400 invalid_request_error: "Could not process image", and because the bad payload now lives in the conversation history, every subsequent turn 400s on replay. The session is permanently bricked until the transcript JSONL is hand-edited.
Repro
# 1. Save an SVG with a .png extension
echo '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>' > /tmp/fake.png
# 2. In a Claude Code session, ask Claude to read it
> Read /tmp/fake.png
# 3. Send any follow-up message
> hi
Expected: tool succeeds or, if rejected, the bad result does not poison subsequent turns.
Actual: every subsequent turn returns
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"},"request_id":"req_..."}
The session cannot be unstuck via /compact or /clear once it's in this state — /compact itself sends the full history and 400s. Recovery requires editing ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl and replacing the offending image content block with a text placeholder, then fully restarting the CLI.
Encountered in the wild
- Claude Code version:
2.1.126 - Working directory:
/private/tmp/nav-iconscontaining UI icon files - Tool call:
Readwithfile_path: /tmp/nav-icons/tab-1-wardrobe.png— file was actually SVG content, extension was.png - Resulting tool_result base64 decoded to:
<svg preserveAspectRatio="none" width="100%" height="100%" ... viewBox="0 0 19 19" ...>whilemedia_type: "image/png"
Suggested fixes
- Sniff magic bytes before setting
media_typein the Read tool. PNG =89 50 4E 47, JPEG =FF D8 FF, GIF =47 49 46 38, WebP =RIFF....WEBP. If bytes don't match any supported raster format, either decline to attach (return text) or correctly tag asimage/svg+xml(and convert/reject if the API doesn't accept it). - Catch the API 400 and offer recovery in-session — e.g. detect
"Could not process image"and prompt the user to drop the offending message rather than retrying forever. - Validate dimensions/size client-side before sending, so users get a useful error instead of an unrecoverable session.
Workaround for affected users
# After fully exiting the session:
# 1. Backup
cp ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl{,.bak}
# 2. Scrub all inline images
python3 -c "
import json, sys
path = sys.argv[1]
def scrub(node):
if isinstance(node, dict):
if node.get('type') == 'image' and isinstance(node.get('source'), dict):
mt = node['source'].get('media_type','?')
node.clear(); node['type']='text'; node['text']=f'[image removed: was {mt}]'
return
for v in node.values(): scrub(v)
elif isinstance(node, list):
for v in node: scrub(v)
out=[]
for line in open(path):
try: d=json.loads(line); scrub(d); out.append(json.dumps(d, ensure_ascii=False)+'\n')
except: out.append(line)
open(path,'w').writelines(out)
" path/to/session.jsonl
# 3. Resume the session
claude --resume <session-id>This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗