claude-code - 💡(How to fix) Fix API Error: 400 messages: text content blocks must be non-empty on session resume when assistant turn contained only tool_use blocks

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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.

Error Message

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

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.

Fix Action

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")

Code Example

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

---

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

---

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")
RAW_BUFFERClick to expand / collapse

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

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING