anthropics / anthropics/claude-code-action
Image MIME mismatch crashes runs: GIFs from issue bodies saved with .png extension, sent to Anthropic API as image/png
- Dominant language
- TypeScript
- Stars
- 8.9k
- Forks
- 2.1k
- Avg merge
- 3d 9h
- Merged PRs (30d)
- 10
Description
## Summary
When the action processes an issue (or PR) whose body references an image hosted on `github.com/user-attachments/assets/`, 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--.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:
1. **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.
2. **`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
- Action: `anthropics/claude-code-action@beta`
- Model: `claude-sonnet-4-20250514`
- API: direct Anthropic via OAuth token (`apiKeySource: ANTHROPIC_API_KEY`)
- Event: `issues.labeled`
- Workflow files: https://github.com/dxos/dxos/blob/main/.github/workflows/claude-composer-triage.yml
- Failing run: https://github.com/dxos/dxos/actions/runs/26347825246
Happy to provide the full job log if useful.
Contributor guide
Research direction
Start with src/github/data/fetcher.ts and trace how user-attachments responses are saved, then inspect the Read tool result builder that assigns image media types. Reproduce with the GIF URL from the issue and verify that the saved format and Anthropic media_type match the downloaded bytes, including non-PNG images.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github, typescript
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100