[BUG] Oversized-image 400 error triggers a retry loop that invalidates prompt cache and inflates cost ~35×
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a request contains an image whose dimensions exceed the API limit (2000px for many-image requests), the API returns a 400 invalid_request_error. Claude Code surfaces this as:
API Error: an image in the conversation could not be processed and was removed. Double press esc to edit your message, or re-read the file if you still need it.
and then silently removes the image and keeps retrying. Each retry mutates the conversation prefix, which invalidates the prompt cache. From that point on, cache reads collapse and the entire context is re-written to the cache on every turn, causing cache-write tokens (and cost) to explode. In my session this single loop accounted for ~66% of the total session cost.
Environment
- Claude Code:
2.1.159→2.1.160(background/clisession) - Model:
claude-opus-4-8 - OS: Linux (WSL2, Ubuntu 24.04)
- Permission mode:
bypassPermissions
Underlying API error
400 {"type":"error","error":{"type":"invalid_request_error", "message":"messages.51.content.20.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels"}, "request_id":"req_..."}
Steps to reproduce
- In a long-running session, have the agent read/attach an image larger than 2000px in some dimension (e.g. a generated plot / screenshot).
- The API rejects the request with the 400 above.
- Observe Claude Code remove the image and continue issuing requests.
- Inspect token usage in the session
.jsonl(orccusage).
Expected behavior
- The oversized image should be downscaled/resized client-side to fit the limit before sending, or
- If it can't be sent, the agent should stop and surface the error once instead of entering a retry loop, and
- Removing content from history should not silently nuke the prompt cache for the rest of the session without warning.
Actual behavior
- The image is stripped and the agent retries 41 times.
- Prompt caching breaks: cache read drops to a constant ~9.7k tokens while cache write balloons and climbs every turn.
Evidence (single session, 295 API calls total)
| Phase | Calls | Avg cache write/call | Avg cache read/call |
|---|---|---|---|
| Before first image error | 254 | ~12.9k | ~252.8k |
| After first image error | 41 | ~452.2k (35×) | ~109.7k |
Per-call cache-write after caching fully broke (call #263 onward) grew monotonically:524k → 531k → 538k → … → 593k tokens — the whole context re-cached every turn.
Cost impact: the 41 post-error calls (14% of all calls) cost ≈ $120 of the ≈ $182 session (~66%). The broken-image loop more than doubled the session cost. (Opus-tier pricing: cache write $6.25 / 1M vs cache read $0.50 / 1M — a ~12× per-token penalty for every re-cached turn.)
Root cause (analysis)
- Image exceeds the
2000pxmany-image limit → API400. - Claude Code mutates the message history to remove the image.
- The cached prefix no longer matches → cache miss for the whole prefix.
- Every subsequent turn re-writes the full (and growing) context at the cache-write rate instead of reusing it at the cache-read rate.
- The agent keeps retrying, so the penalty repeats and compounds.
Suggested fixes
- Pre-validate & auto-resize images to the API's max dimensions (and per-image / many-image rules) before sending.
- Don't enter an unbounded retry loop on
invalid_request_errorfor images — fail fast and ask the user once. - When history is edited mid-session, warn that the prompt cache will be invalidated, and/or restructure edits to preserve the cacheable prefix.
- Optionally surface a cost/cache-miss warning when cache-read drops to ~0 across many consecutive turns.
Notes
Detected by parsing the session transcript and summing per-request usage (deduped by requestId); cache-write tokens ar
What Should Happen?
Expected behavior
- The oversized image should be downscaled/resized client-side to fit the limit before sending, or
- If it can't be sent, the agent should stop and surface the error once instead of entering a retry loop, and
- Removing content from history should not silently nuke the prompt cache for the rest of the session without warning.
Error Messages/Logs
400 {"type":"error","error":{"type":"invalid_request_error", "message":"messages.51.content.20.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels"}, "request_id":"req_..."}
Steps to Reproduce
- In a long-running session, have the agent read/attach an image larger than 2000px in some dimension (e.g. a generated plot / screenshot).
- The API rejects the request with the 400 above.
- Observe Claude Code remove the image and continue issuing requests.
- Inspect token usage in the session
.jsonl(orccusage).
Expected behavior
- The oversized image should be downscaled/resized client-side to fit the limit before sending, or
- If it can't be sent, the agent should stop and surface the error once instead of entering a retry loop, and
- Removing content from history should not silently nuke the prompt cache for the rest of the session without warning.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.160
Platform
Anthropic API
Operating System
Linux
Terminal/Shell
Bash
Additional Information
_No response_
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗