-
-
Notifications
You must be signed in to change notification settings - Fork 56k
Description
Description
When Claude responds with tool calls, the assistant messages are stored with empty text blocks like {"type": "text", "text": ""} alongside the toolCall blocks. When these messages are later sent back to the Claude API as conversation history, Claude rejects them with:
400 messages.X: all messages must have non-empty content except for the optional final assistant message
Steps to Reproduce
- Start a conversation via WhatsApp
- Send a message that triggers Claude to use tools (e.g., "hi" which triggers reading context files)
- Send 2-3 more messages
- Eventually get the 400 error
Example Session Content
The stored assistant message looks like:
{
"type": "message",
"message": {
"role": "assistant",
"content": [
{"type": "text", "text": ""}, // <-- This empty text block causes the issue
{"type": "toolCall", "id": "toolu_xxx", "name": "read", "arguments": {...}}
]
}
}Root Cause
Claude's API responses include empty text blocks when the model goes directly to tool use. These are stored verbatim in the session JSONL files. When the conversation history is sent back to Claude, these empty text blocks violate the API's message content requirements.
Suggested Fix
Filter out empty text blocks when storing assistant messages to session files. Something like:
// Before storing assistant message
message.content = message.content.filter(block =>
block.type !== 'text' || (block.text && block.text.length > 0)
);Or alternatively, filter them when loading the conversation history before sending to the API.
Current Workaround
I'm running a real-time session cleaner service using inotifywait that monitors session files and removes empty text blocks immediately:
jq -c '
if .type == "message" and .message.role == "assistant" then
.message.content |= [.[] | select(
(.type != "text") or ((.text // "") != "")
)] |
if (.message.content | length) > 0 then . else empty end
else
.
end
' session.jsonlEnvironment
- ClawdBot via WhatsApp surface
- Claude Haiku 4.5 model
- Using CLIProxyAPI for OAuth
🤖 Generated with Claude Code