Session resume corrupts JSONL: file-history-snapshot messageId collides with message uuid

Resolved 💬 2 comments Opened Mar 20, 2026 by wzxklm Closed Mar 20, 2026

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

  1. Start a conversation in Claude Code (VS Code extension)
  2. Use the Agent tool (subagent) during the conversation — observe the summary output is displayed
  3. End the session
  4. Resume the session
  5. 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

View original on GitHub ↗

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