"API Error: 400 due to tool use concurrency issues" — Conversation permanently broken after context compaction

Resolved 💬 8 comments Opened Mar 22, 2026 by dataexplora Closed May 14, 2026

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?

Summary

A long-running conversation becomes permanently unusable with API
Error: 400 due to tool use concurrency issues. Neither /rewind
(even 40+ messages back) nor /compact can recover it. The root
cause is that user messages entered via voice dictation (or
certain input methods) are stored in the JSONL conversation file
with content as a plain string instead of the expected array
format. This works fine during normal operation, but when
compaction runs, the reconstruction logic fails to merge these
string-content messages with adjacent same-role messages,
producing consecutive same-role messages that violate the API
protocol.

Environment

  • Claude Code version: 2.1.56
  • OS: Linux (Arch)
  • Shell: PowerShell 7

Root Cause Analysis

  1. String vs Array content format

The JSONL conversation log stores user messages in two different
formats depending on input method:

Typed messages (correct):
{"type": "user", "message": {"role": "user", "content": [{"type":
"text", "text": "the message"}]}}

Voice/dictated messages (incorrect):
{"type": "user", "message": {"role": "user", "content": "the
message"}}

In the affected conversation, 20 out of 124 user messages had
string content. All were informal Greek text consistent with voice
dictation input.

  1. Compaction breaks message alternation

During normal operation, Claude Code handles both formats fine —
it presumably converts string content to array format before
sending to the API. However, when context compaction runs, the
reconstruction logic tries to merge consecutive same-role messages
by combining their content arrays. When it encounters a
string-content message adjacent to an array-content message of the
same role, the merge fails:

user (array: tool_result) → can merge ✓
user (array: text) → can merge ✓
user (string: "text") → CANNOT merge with array ✗

This produces consecutive same-role messages in the API request,
which the Anthropic API rejects with HTTP 400.

  1. Additionally, metadata entries between same-role messages

file-history-snapshot and progress entries between consecutive
assistant messages (a normal JSONL pattern) were also not being
collapsed during reconstruction:

assistant (text) → line 544
file-history-snapshot → line 545 (breaks the merge)
assistant (tool_use: Edit) → line 546

This created 7 additional consecutive same-role violations after
merging.

  1. Compact subagents also corrupted

The compaction process spawns subagents
(subagents/agent-acompact-*.jsonl) that inherit the same corrupted
message structure. These subagents also fail with 400, creating a
cascade where:

  • Main conversation tries to compact → spawns compact agent
  • Compact agent inherits broken messages → fails with 400
  • Main conversation gets 400 → appends error to JSONL
  • User retries → more broken entries appended
  • /compact retried → spawns another broken compact agent

Manual Fix Applied

The conversation was recovered by manually editing the JSONL file:

  1. Removed all API Error: 400 assistant messages and their

triggering user messages

  1. Converted all string content fields to array format: "content":

"text" → "content": [{"type": "text", "text": "text"}]

  1. Merged consecutive same-role messages by combining their

content arrays, reordering metadata entries so they don't
interrupt same-role sequences

  1. Deleted corrupted compact subagent files

(agent-acompact-*.jsonl)

  1. Truncated trailing orphaned messages after the last valid

assistant response

Suggested Fix

In the code path that serializes user messages to JSONL, normalize
content to array format regardless of input method:

// Before writing to JSONL:
if (typeof message.content === 'string') {
message.content = [{ type: 'text', text: message.content }];
}

And in the compaction/reconstruction logic, add the same
normalization before attempting to merge consecutive messages.

Impact

  • Severity: High — conversation is permanently unusable, all work

context is lost

  • Data loss: No code changes lost (files on disk are fine), but

conversation context/history is effectively destroyed

  • Workaround difficulty: Requires manual JSONL surgery with Python

scripts — not something a typical user can do

What Should Happen?

Expected Behavior

  • Voice/dictated input should be stored in the same array format

as typed input, OR

  • The compaction/reconstruction logic should handle string content

gracefully (convert to array before merging)

  • /rewind should be able to recover from this state by going back

far enough

  • Compaction failure should not permanently corrupt the

conversation

Error Messages/Logs

API Error: 400 due to tool use concurrency issues. Run /rewind to
recover the conversation.

  This is the only error shown — no stack trace, no additional
  details. The same error appears regardless of message content,
  including after running `/rewind` (tested up to 40 messages back)
  and `/compact`.

The error is also stored in the JSONL as a regular assistant
message:

{"type": "assistant", "message": {"role": "assistant", "content":
[{"type": "text", "text": "API Error: 400 due to tool use
concurrency issues. Run /rewind to recover the conversation."}]}}

Steps to Reproduce

  1. Start a long conversation (this one had ~240 merged API

messages, 700+ JSONL entries)

  1. During the conversation, send several messages via voice

dictation / speech-to-text input

  1. Continue working until the context window fills up enough to

trigger automatic compaction

  1. After compaction, every subsequent message returns: API Error:

400 due to tool use concurrency issues

  1. /rewind (any number of messages) does not fix it
  1. /compact does not fix it — it actually makes it worse by

appending more broken entries

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.81 (Claude Code)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

PowerShell

Additional Information

_No response_

View original on GitHub ↗

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