[Regression] advisor() still breaks sessions post-compaction via parentUuid tree mismatch -- root cause identified, workaround script included
Still happening as of May 2026
Issues #53365, #55535, #56515, #49994, #50223 were all auto-closed as duplicates with zero human resolution or fix. This bug is alive. Reporting with the specific root cause not present in any prior issue.
Root cause (new finding)
Claude Code stores conversation turns as a JSONL tree linked by parentUuid. When auto-compaction runs during a long session, it can cause the server_tool_use (advisor call) and its corresponding advisor_tool_result to land on different branches of the conversation tree — i.e. their parentUuid fields point to different ancestors.
When the API reconstructs the message sequence for the next request, it walks a single branch. The advisor_tool_result appears on the path but its paired server_tool_use does not, triggering:
400 messages.N.content.0: unexpected `tool_use_id` found in `advisor_tool_result` blocks: srvtoolu_XXXXXX.
Each `advisor_tool_result` block must have a corresponding `server_tool_use` block before it.
Concretely, in my broken session the first 3 advisor pairs were correctly linked:
advisor_tool_result.parentUuid==server_tool_use.uuid(correct)
The 4th pair was broken:
server_tool_useuuid=5baa7720-..., parentUuid=cd2695f1-...advisor_tool_resultparentUuid=9497d413-...(different parent, different branch)
The session becomes permanently unrecoverable -- every subsequent API call fails 400. /compact makes it worse (it is what caused the split). /rewind cannot reach back far enough once the session is long.
Reproduction
- Run a long session (795 JSONL records in mine) with multiple
advisor()calls - Let auto-compaction fire mid-session
- Next prompt throws 400; every prompt after also throws 400
Workaround (until fixed)
Find the broken session JSONL at ~/.claude/projects/<slug>/<session-id>.jsonl and strip the orphaned blocks:
import json, shutil
from pathlib import Path
path = Path('~/.claude/projects/YOUR-PROJECT/YOUR-SESSION.jsonl').expanduser()
shutil.copy2(path, path.with_suffix('.jsonl.bak'))
with open(path) as f:
lines = f.readlines()
fixed = []
for line in lines:
if 'srvtoolu_' not in line:
fixed.append(line)
continue
obj = json.loads(line)
msg = obj.get('message', {})
content = msg.get('content', []) if msg else []
if isinstance(content, list):
new_content = [b for b in content if not (
isinstance(b, dict) and b.get('type') in ('advisor_tool_result', 'server_tool_use')
and (b.get('tool_use_id', '') + b.get('id', '')).startswith('srvtoolu_')
)]
if len(new_content) != len(content):
if new_content:
msg['content'] = new_content
obj['message'] = msg
fixed.append(json.dumps(obj) + '\n')
continue # drop empty records
fixed.append(line)
with open(path, 'w') as f:
f.writelines(fixed)
After running, claude --resume <session-id> works normally.
Fix direction
Compaction should either:
- Preserve
server_tool_use/advisor_tool_resultpairs on the same branch, or - Detect and strip orphaned pairs before sending to the API rather than failing hard
---
Note to triage bot: This is NOT a duplicate of #53365, #55535, or #56515. Those were closed without a fix. This provides the specific root cause (parentUuid tree mismatch during compaction) and a working recovery script not present in any prior report.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗