[BUG] Local slash command (e.g. /color) firing during in-flight advisor() call splits assistant message in JSONL → session 400s with "advisor_tool_result has no server_tool_use"
Summary
If a local slash command (confirmed with /color, likely any command that writes type: system local-command lines) fires while an advisor() call is in flight, Claude Code writes the two system lines into the session JSONL between the server_tool_use entry and its advisor_tool_result entry — even though all three share the same message.id. On replay, the client reconstructs them as two separate assistant messages, orphaning the result block. The API then rejects every subsequent turn with:
API Error: 400 messages.N.content.0: unexpected `tool_use_id` found in `advisor_tool_result` blocks: srvtoolu_XXX.
Each `advisor_tool_result` block must have a corresponding `server_tool_use` block before it.
Distinct from #49994 (server-side encrypted-payload TTL) — this one is purely local JSONL construction.
Repro
- Start a Claude Code session.
- Prompt something that causes the assistant to call
advisor(). - While the
advisor()call is in flight (or during the same assistant turn), type a local slash command like/color cyan. - The assistant turn completes.
- Send any next prompt →
400. Session is dead for normal use.
JSONL evidence
From an affected session (~/.claude/projects/<proj>/<session>.jsonl), five consecutive lines all belong to the same message.id but have a slash command wedged through the middle:
| line | role/type | message.id | content type | parent chain |
|---|---|---|---|---|
| N+0 | assistant | msg_AAA | thinking | prior user msg |
| N+1 | assistant | msg_AAA | server_tool_use (srvtoolu_XXX) | N+0 |
| N+2 | system | — | /color command | N+1 |
| N+3 | system | — | /color stdout | N+2 |
| N+4 | assistant | msg_AAA | advisor_tool_result (srvtoolu_XXX) | N+3 ← points at system line |
| N+5 | assistant | msg_AAA | thinking | N+4 |
Because N+4's parentUuid points at a system line (N+3), the replay logic treats it as a separate assistant message containing only the advisor_tool_result block — hence the "no server_tool_use before it" validation failure on the server.
Root cause (inferred)
Claude Code appears to serialize an assistant turn into multiple JSONL lines as blocks arrive. The local-command writer is not coordinated with the streaming-response writer, so when a slash command fires mid-turn, its system entries get interleaved and the next assistant block's parentUuid chains off the system line instead of the previous assistant block of the same message.id. On replay, the parent-chain reconstruction wins and splits one semantic API message into two structurally-invalid ones.
Fix
Client-side JSONL patch (session must be closed first to avoid flush overwrite):
- Delete the two interleaved
"type":"system"local-command lines. - Rewire the
advisor_tool_resultline'sparentUuidfrom the deleted system line's UUID to theserver_tool_useline's UUID. - Verify
advisor_tool_result.parentUuid == server_tool_use.uuidand both sharemessage.id.
After that the API replay succeeds and the session resumes normally. Manual, 30-second fix but not something most users should be doing by hand.
Suggested upstream fix
Either:
- Serialize by
message.id, not parent chain, when reconstructing API messages for replay. All lines with the samemessage.idshould collapse into one API message regardless of interleaved system entries. - Defer local-command writes until the current assistant turn has fully flushed.
- Validate on write — if an assistant block would be written with a
parentUuidpointing at asystemline while itsmessage.idmatches the previous assistant block, rewrite the parent to that previous assistant UUID.
Environment
- Claude Code: observed on
2.1.114 - Platform: Linux (Fedora 43), x86_64
- Model:
claude-opus-4-7[1m] - Permission mode: default
- First hit: 2026-04-18
Happy to share sanitized JSONL excerpts if helpful.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗