API Error: 400 messages: text content blocks must be non-empty on session resume when assistant turn contained only tool_use blocks

Resolved 💬 3 comments Opened May 26, 2026 by markmhendrickson Closed Jun 26, 2026

Summary

When resuming a Claude Code session, the Anthropic API rejects the replayed conversation history with:

API Error: 400 messages: text content blocks must be non-empty

This makes the session unresumable and is especially severe for remote/headless sessions where starting a new session is not trivial.

Root cause

The harness serializes assistant turns that contained only tool_use blocks (no accompanying text) by adding an empty "" text block. The Anthropic API rejects message arrays containing {"type": "text", "text": ""}.

An affected assistant turn in the session JSONL looks like:

{
  "role": "assistant",
  "content": [
    {"type": "text", "text": ""},
    {"type": "tool_use", "id": "...", "name": "...", "input": {}}
  ]
}

The empty text block is invalid per the API contract. It should be omitted entirely when there is no text.

Steps to reproduce

  1. Start a Claude Code session.
  2. Trigger a turn where the model responds with one or more tool calls and no text (e.g. a rapid retrieval-only response with no preamble).
  3. End or interrupt the session.
  4. Attempt to resume the session via --resume or by reopening the project.

Result: API Error: 400 messages: text content blocks must be non-empty

Expected: Session resumes normally, or the harness strips/skips empty text blocks before replaying history.

Workaround

Strip empty text blocks from the session JSONL manually before resuming:

import json

path = "~/.claude/projects/<project>/<session-id>.jsonl"
fixed = []
with open(path) as f:
    for line in f:
        entry = json.loads(line)
        msg = entry.get("message", {})
        if msg.get("role") == "assistant":
            content = msg.get("content", [])
            msg["content"] = [
                b for b in content
                if not (b.get("type") == "text" and b.get("text", "").strip() == "")
            ]
        fixed.append(entry)
with open(path, "w") as f:
    for entry in fixed:
        f.write(json.dumps(entry) + "\n")

Suggested fix

When serializing assistant turns for the API messages array, skip any {"type": "text", "text": ""} blocks. Alternatively, validate the content array before sending and strip empty text blocks at the API call site.

Environment

  • Claude Code (CLI)
  • Affected on session resume (--resume or project reopen)
  • Not triggered by a specific model version — the issue is in harness serialization

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗