"API Error: 400 due to tool use concurrency issues" — Conversation permanently broken after context compaction
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
- 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.
- 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.
- 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.
- 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:
- Removed all API Error: 400 assistant messages and their
triggering user messages
- Converted all string content fields to array format: "content":
"text" → "content": [{"type": "text", "text": "text"}]
- Merged consecutive same-role messages by combining their
content arrays, reordering metadata entries so they don't
interrupt same-role sequences
- Deleted corrupted compact subagent files
(agent-acompact-*.jsonl)
- 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
- Start a long conversation (this one had ~240 merged API
messages, 700+ JSONL entries)
- During the conversation, send several messages via voice
dictation / speech-to-text input
- Continue working until the context window fills up enough to
trigger automatic compaction
- After compaction, every subsequent message returns: API Error:
400 due to tool use concurrency issues
- /rewind (any number of messages) does not fix it
- /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_
This issue has 8 comments on GitHub. Read the full discussion on GitHub ↗