Session rename mid-server-tool-call injects a turn that permanently corrupts the transcript (400 on every future prompt)

Open 💬 4 comments Opened Jul 2, 2026 by mmartinez-infra

Summary

Renaming a session (custom title) while a server_tool_use call (e.g. the built-in advisor tool) is in flight injects a system-reminder as a synthetic user turn in the transcript, landing it between the server_tool_use block and its matching advisor_tool_result block. Because server-tool call + result must live in the same API message, this splits them across two messages on the next resume/request, and the Anthropic API rejects the entire history with a 400 on every subsequent turn — permanently breaking the session until the transcript file is hand-edited.

Reproduction

  1. Start a session and trigger a server-side tool call that returns asynchronously (in our case, the advisor tool).
  2. While the call is in flight, rename the session (set a custom title) — e.g. via whatever UI/flow assigns customTitle/agent-name.
  3. Let the tool call resolve and continue the conversation, then send further prompts.
  4. Every subsequent prompt now fails with:

``
API Error: 400 messages.N.content.0: unexpected
tool_use_id found in advisor_tool_result blocks: srvtoolu_XXXX. Each advisor_tool_result block must have a corresponding server_tool_use block before it.
``

Root cause (confirmed by inspecting the session .jsonl)

In the broken transcript, the three lines around the failure were:

{"type":"assistant", ..., "uuid":"4320b59f-...", "message":{"content":[{"type":"server_tool_use","id":"srvtoolu_01YZBP7b9c3YoD3NXiLgXsVb","name":"advisor","input":{}}]}}
{"type":"user","isMeta":true, "parentUuid":"4320b59f-...", "message":{"role":"user","content":"<system-reminder>\nThe user named this session \"...\". This may indicate the session's focus or intent.\n</system-reminder>"}}
{"type":"assistant", "parentUuid":"<the user line above>", "message":{"content":[{"type":"advisor_tool_result","tool_use_id":"srvtoolu_01YZBP7b9c3YoD3NXiLgXsVb", ...}]}}

When the harness replays/merges the transcript into the API messages array, consecutive same-role entries are folded into one logical message (confirmed by other server_tool_use/advisor_tool_result pairs in the same file that have no interruption and work fine). The injected isMeta user turn breaks that fold here, producing:

  • message A (assistant): [..., server_tool_use]
  • message B (user): [system-reminder text]
  • message C (assistant): [advisor_tool_result, ...]

advisor_tool_result in message C no longer has its server_tool_use in the same message → 400 from the API → every future turn in the session fails identically, since the full (corrupted) history is resent each time.

Impact

  • Session becomes permanently unusable for every future prompt, not just the one where the race happened.
  • No user-facing recovery path — we had to manually locate the offending lines in ~/.claude/projects/<project>/<session>.jsonl, delete the injected isMeta system-reminder line, and repoint the following message's parentUuid to restore a valid chain, then re-verify the merged message structure by hand.

Suggested fix

  • Don't inject system-reminder (or any synthetic) user turns while a server-side tool call is awaiting its result — queue the reminder and deliver it only once the current turn (tool_use + its result) is fully closed.
  • Defensively, on session load/resume, detect and either coalesce or drop any zero-content-value synthetic turn that would split a server_tool_use/matching result pair, rather than sending it to the API and hard-failing.

Happy to share the (redacted) transcript snippet if useful.

View original on GitHub ↗

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