API 400 "cache_control cannot be set for empty text blocks" when user message contains only an image
Summary
When a user sends a message containing only an image (no text), the client serializes the message content as [image_block, empty_text_block]. On the next API call, the prompt-caching logic attaches cache_control: ephemeral to the last content block — which is the empty text block. Anthropic API rejects this with 400 invalid_request_error: cache_control cannot be set for empty text blocks, and the session becomes permanently stuck: every subsequent turn replays the broken message and 400s again.
Environment
- Claude Code version:
2.1.118(reproduced); likely same on later versions, no fix in changelog - Entry point:
sdk-cli(viaclaude remote-controlbackground daemon on Linux). I expect the bug is in the message serialization layer and reproduces from the desktop app too - Platform: Linux x86_64, Node v22.22.2
- Model:
claude-opus-4-7(1M context, Max plan)
Reproduction Steps
- Open any session
- Paste / drag an image into the input
- Do not type anything else — press Enter immediately
- Wait for the model reply (succeeds)
- Send any next message
- → API call fails with
400 messages.N.content.M.text: cache_control cannot be set for empty text blocks - → Session is now permanently broken; every turn replays the bad message
Expected Behavior
Either:
- Client should not write an empty text block when the user message contains only an image, or
- Prompt-caching layer should skip empty text blocks when choosing where to attach
cache_control
Actual Error
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "messages.60.content.1.text: cache_control cannot be set for empty text blocks"
},
"request_id": "req_011Cb5dtKrgn5pejV1M7Thyi"
}
Reproduction Evidence
The offending message in the session jsonl:
{
"type": "user",
"message": {
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "..."}},
{"type": "text", "text": ""}
]
}
}
The empty text block is the smoking gun.
Workaround
User-side: always include at least one non-empty character when sending an image-only message.
Recovery for an already-broken session: kill the daemon (or close the app), edit the session jsonl to drop the empty text block from the offending message (or replace "text": "" with "text": "."), restart.
Suggested Fix
In client-side content normalization, filter blocks where type === "text" && (text ?? "").length === 0 before sending. The same filter would protect against future variants of this bug (e.g. user clears text after attaching an image)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗