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

Resolved 💬 2 comments Opened Apr 18, 2026 by AlainDor Closed May 26, 2026

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

  1. Start a Claude Code session.
  2. Prompt something that causes the assistant to call advisor().
  3. While the advisor() call is in flight (or during the same assistant turn), type a local slash command like /color cyan.
  4. The assistant turn completes.
  5. 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):

  1. Delete the two interleaved "type":"system" local-command lines.
  2. Rewire the advisor_tool_result line's parentUuid from the deleted system line's UUID to the server_tool_use line's UUID.
  3. Verify advisor_tool_result.parentUuid == server_tool_use.uuid and both share message.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 same message.id should 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 parentUuid pointing at a system line while its message.id matches 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.

View original on GitHub ↗

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