Session resume corrupts JSONL: file-history-snapshot messageId collides with message uuid
Bug Description
When resuming a session, Claude Code writes file-history-snapshot entries whose messageId values collide with existing message uuid values in the JSONL file. This causes parentUuid chain traversal to break, making a significant portion of conversation history unreachable after resume.
Evidence
Analyzed the JSONL file of the resumed session with a diagnostic script:
| Metric | Value |
|--------|-------|
| Total entries | 330 |
| messageId/uuid collisions | 34 |
| Broken parentUuid refs | 0 |
| Entries with uuid | 251 |
| Reachable from last entry | 190 (75%) |
| Unreachable (lost) | 61 (25%) |
Collision pattern
Line 3: type=user, uuid=842f0bfe-2c1e-44c0-8655-74ff68c02903
...
Line 217: type=file-history-snapshot, messageId=842f0bfe-2c1e-44c0-8655-74ff68c02903 ← COLLISION
Lines 217-228 contain 12 consecutive file-history-snapshot entries, all with messageId values that duplicate earlier message uuid values. These appear to have been written during the resume operation.
Impact
- Subagent summary output disappears — messages that were visible during the live session become unreachable after resume
- 25% of conversation history lost — only 75% of messages are reachable via parentUuid chain traversal from the last entry
- Data corruption at write time — downgrading versions does not fix already-corrupted files (consistent with #24304 findings)
Steps to Reproduce
- Start a conversation in Claude Code (VS Code extension)
- Use the Agent tool (subagent) during the conversation — observe the summary output is displayed
- End the session
- Resume the session
- The subagent summary output is missing — both from the UI and from Claude's own context
Root Cause Analysis
The file-history-snapshot entries written during resume reuse the uuid of the original messages as their messageId. When the parentUuid chain is traversed, these collisions create ambiguity — the traversal can jump to a snapshot node instead of the real message node, breaking the chain and orphaning downstream messages.
Environment
- Claude Code running in VS Code extension
- Model: claude-opus-4-6 (1M context)
- Platform: Linux
- Context usage at time of issue: 7% (71.7k / 1000k) — well below compaction threshold
Diagnostic Script
import json
session_file = "path/to/session.jsonl"
entries = []
with open(session_file, 'r') as f:
for i, line in enumerate(f):
line = line.strip()
if not line:
continue
entries.append((i + 1, json.loads(line)))
all_uuids = {}
collisions = 0
for line_no, entry in entries:
uuid = entry.get('uuid')
if uuid:
all_uuids[uuid] = line_no
for line_no, entry in entries:
msg_id = entry.get('messageId')
uuid = entry.get('uuid')
if msg_id and msg_id in all_uuids and msg_id != uuid:
collisions += 1
print(f"COLLISION at line {line_no}: messageId={msg_id[:16]}... collides with uuid at line {all_uuids[msg_id]}")
# Chain reachability
uuid_to_entry = {e.get('uuid'): e for _, e in entries if e.get('uuid')}
last_uuid = entries[-1][1].get('uuid')
reachable = set()
current = last_uuid
while current and current in uuid_to_entry:
reachable.add(current)
current = uuid_to_entry[current].get('parentUuid')
total_with_uuid = len(uuid_to_entry)
print(f"\nCollisions: {collisions}")
print(f"Reachable: {len(reachable)}/{total_with_uuid} ({len(reachable)*100//total_with_uuid}%)")
Related Issues
- #24304 — Conversation history missing on resume (same collision pattern documented)
- #23821 — Subagent output files lost after context compaction
- #11712 — Subagent resume missing all user prompts
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗