Summary
When the action processes an issue (or PR) whose body references an image hosted on github.com/user-attachments/assets/<uuid>, it downloads the bytes and saves them locally with a .png extension regardless of the actual content type. If Claude later opens that file with the Read tool, the tool result is sent to the Anthropic API with media_type: image/png but the bytes are a GIF (or other non-PNG format), and the API correctly rejects the request with HTTP 400.
This is reproducible end-to-end on a run that otherwise authenticates, triggers, and progresses normally — the run burns through ~$0.11 / 7 turns before failing irrecoverably.
Reproduction
- Action ref:
anthropics/claude-code-action@beta (commit de8e0b9 at time of writing).
- Trigger:
issues: [opened, labeled] workflow with direct_prompt: and claude_code_oauth_token: set; tag mode (default).
- Subject: an old issue whose body embeds a
user-attachments image.
The image URL in our case is https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f. Downloading it and inspecting:
$ curl -sL 'https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f' | head -c 8 | xxd
00000000: 4749 4638 3961 8000 GIF89a..
It's a GIF. But the action saved it as /tmp/github-images/image-1779583737858-0.png.
Error (from our run log)
{
"type": "assistant",
"message": {
"content": [{
"type": "tool_use",
"name": "Read",
"input": { "file_path": "/tmp/github-images/image-1779583737858-0.png" }
}]
}
}
Tool result:
{
"type": "user",
"message": {
"content": [{
"tool_use_id": "toolu_...",
"type": "tool_result",
"content": [{
"type": "image",
"source": {
"type": "base64",
"data": "R0lGODlhgACAAPQA…", // ← base64-decodes to "GIF89a…"
"media_type": "image/png" // ← but media_type claims PNG
}
}]
}]
}
}
Anthropic API response (HTTP 400):
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "messages.4.content.0.tool_result.content.0.image.source.base64: The image was specified using the image/png media type, but the image appears to be a image/gif image"
},
"request_id": "req_011CbLUWBhSwNTHFzj8a5uF4"
}
Run terminated with is_error: true, total_cost_usd: 0.11139810, num_turns: 7. Process exit code 1.
Suspected root cause
The action's image-downloading code (around src/github/data/fetcher.ts per a code-search hit for "github-images" + the image-<timestamp>-<i>.png naming pattern) hard-codes a .png extension when persisting the downloaded blob. Downstream, the Read tool's MIME inference appears to be extension-based (defaulting to image/png for .png files), so the tool_result misrepresents the content type and the Anthropic API rightly rejects it.
Two fixable layers:
- Action download step — sniff the magic bytes (or the response
Content-Type) when saving and choose .gif / .jpg / .webp / etc. accordingly. GitHub's user-attachments endpoint returns the correct Content-Type header for the original asset.
Read tool result builder — content-sniff the bytes before setting media_type rather than trusting the file extension.
Either fix individually should resolve it; both is belt-and-braces.
Impact
Any issue/PR triage workflow that runs Claude over a body containing a user-attachments GIF (animated screencaptures, the spinner the action itself uses in some templates, embedded animations, etc.) will fail mid-run after burning tokens. The failure isn't recoverable within the run — the malformed message stays in the conversation history, so subsequent retries hit the same 400.
Workarounds we tried
- Removing the offending image from the issue body fixes the immediate run (per a follow-up retry by the issue reporter).
- No action-side knob exists to disable image fetching from issue bodies, so users with GIF-rich histories can't avoid the failure mode without editing each issue.
Environment
Happy to provide the full job log if useful.
Summary
When the action processes an issue (or PR) whose body references an image hosted on
github.com/user-attachments/assets/<uuid>, it downloads the bytes and saves them locally with a.pngextension regardless of the actual content type. If Claude later opens that file with theReadtool, the tool result is sent to the Anthropic API withmedia_type: image/pngbut the bytes are a GIF (or other non-PNG format), and the API correctly rejects the request with HTTP 400.This is reproducible end-to-end on a run that otherwise authenticates, triggers, and progresses normally — the run burns through ~$0.11 / 7 turns before failing irrecoverably.
Reproduction
anthropics/claude-code-action@beta(commitde8e0b9at time of writing).issues: [opened, labeled]workflow withdirect_prompt:andclaude_code_oauth_token:set; tag mode (default).user-attachmentsimage.The image URL in our case is
https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f. Downloading it and inspecting:It's a GIF. But the action saved it as
/tmp/github-images/image-1779583737858-0.png.Error (from our run log)
Tool result:
Anthropic API response (HTTP 400):
Run terminated with
is_error: true,total_cost_usd: 0.11139810,num_turns: 7. Process exit code 1.Suspected root cause
The action's image-downloading code (around
src/github/data/fetcher.tsper a code-search hit for "github-images" + theimage-<timestamp>-<i>.pngnaming pattern) hard-codes a.pngextension when persisting the downloaded blob. Downstream, theReadtool's MIME inference appears to be extension-based (defaulting toimage/pngfor.pngfiles), so the tool_result misrepresents the content type and the Anthropic API rightly rejects it.Two fixable layers:
Content-Type) when saving and choose.gif/.jpg/.webp/ etc. accordingly. GitHub'suser-attachmentsendpoint returns the correctContent-Typeheader for the original asset.Readtool result builder — content-sniff the bytes before settingmedia_typerather than trusting the file extension.Either fix individually should resolve it; both is belt-and-braces.
Impact
Any issue/PR triage workflow that runs Claude over a body containing a
user-attachmentsGIF (animated screencaptures, the spinner the action itself uses in some templates, embedded animations, etc.) will fail mid-run after burning tokens. The failure isn't recoverable within the run — the malformed message stays in the conversation history, so subsequent retries hit the same 400.Workarounds we tried
Environment
anthropics/claude-code-action@betaclaude-sonnet-4-20250514apiKeySource: ANTHROPIC_API_KEY)issues.labeledHappy to provide the full job log if useful.