claude-code - 💡(How to fix) Fix Resume loses most context: parentUuid chain breaks on unpersisted messages

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When a session is resumed with --resume or /resume, most conversation context is silently lost. The parentUuid chain in the session JSONL contains references to UUIDs that were never written to the file. Walking the chain on resume stops at the first such break, making everything before it unreachable.

Error Message

The dangling system entries all have cause.code: "ECONNRESET" and retry metadata (retryAttempt, maxRetries, retryInMs). These appear to be API error records whose parent references internal conversation state that wasn't persisted.

Root Cause

Root cause (observed, not confirmed from source)

Code Example

#!/usr/bin/env python3
"""Diagnose parentUuid chain breaks in Claude Code session JSONL files."""
import json, sys
from pathlib import Path

def diagnose(path):
    entries = []
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line:
                try: entries.append(json.loads(line))
                except: pass

    by_uuid = {e["uuid"]: e for e in entries if e.get("uuid")}
    dangling = [i for i, e in enumerate(entries)
                if e.get("parentUuid") and e["parentUuid"] not in by_uuid]

    # Walk chain from latest entry
    last = next((e["uuid"] for e in reversed(entries) if e.get("uuid")), None)
    chain, current, visited = 0, last, set()
    while current and current not in visited:
        visited.add(current)
        obj = by_uuid.get(current)
        if not obj: break
        chain += 1
        current = obj.get("parentUuid")

    return len(by_uuid), len(dangling), chain

if __name__ == "__main__":
    d = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
    files = sorted(d.glob("*.jsonl"), key=lambda p: p.stat().st_size, reverse=True)
    affected = 0
    print(f"{'session':>14} {'size':>10} {'w/uuid':>7} {'dangle':>7} {'chain':>7} {'reach%':>7}")
    print("-" * 60)
    for p in files:
        if p.stat().st_size < 5000: continue
        wu, dg, ch = diagnose(p)
        if dg > 0:
            affected += 1
            pct = (ch / wu * 100) if wu else 0
            print(f"{p.stem[:14]:>14} {p.stat().st_size:>10,} {wu:>7} {dg:>7} {ch:>7} {pct:>6.1f}%")
    print(f"\n{affected} of {len(files)} sessions have dangling parentUuid refs")

---

session       size   w/uuid  dangle   chain  reach%
------------------------------------------------------------
  f2929101-147 481,045,312      139       8       2    1.4%
  53eae523-807 53,093,787      254      35       3    1.2%
  946c4893-494 14,299,252     5783      22     301    5.2%
  1fa94892-324  9,860,472     2021      79       7    0.3%
  46c1e03f-4f3  7,065,886     2612    1031       3    0.1%

213 of 230 sessions have dangling parentUuid refs
RAW_BUFFERClick to expand / collapse

Summary

When a session is resumed with --resume or /resume, most conversation context is silently lost. The parentUuid chain in the session JSONL contains references to UUIDs that were never written to the file. Walking the chain on resume stops at the first such break, making everything before it unreachable.

Impact

In my project (~200 session files), every session over 10KB has dangling parentUuid references. Typical reachability on resume is 1-10% of entries. In one representative session:

  • 5,783 entries with UUIDs
  • 22 dangling parentUuid references (pointing to UUIDs that don't exist in the file)
  • Active chain on resume: 301 entries (5.2% reachable)
  • 0 compact/summary markers (auto-compact is disabled)
  • Context bar went from ~35% to ~9% on resume

The lost context includes all tool results, reasoning, and conversation history before the first chain break. Users see a dramatically smaller context percentage after resume with no indication that content was lost.

Root cause (observed, not confirmed from source)

In the working portion of the chain, every entry's parentUuid points exactly to the previous entry — zero adjacency gaps. But at 22 specific points, an assistant or system entry's parentUuid points to a UUID that was never written to the JSONL as any entry's uuid.

The dangling system entries all have cause.code: "ECONNRESET" and retry metadata (retryAttempt, maxRetries, retryInMs). These appear to be API error records whose parent references internal conversation state that wasn't persisted.

The dangling assistant entries are normal responses whose parentUuid points to what appears to be an ephemeral message (system injection, hook output, or retry state) that was part of the API call but never appended to the JSONL.

Reproduction

Save this as diagnose_resume.py and run in your session directory (~/.claude/projects/<slug>/):

#!/usr/bin/env python3
"""Diagnose parentUuid chain breaks in Claude Code session JSONL files."""
import json, sys
from pathlib import Path

def diagnose(path):
    entries = []
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line:
                try: entries.append(json.loads(line))
                except: pass

    by_uuid = {e["uuid"]: e for e in entries if e.get("uuid")}
    dangling = [i for i, e in enumerate(entries)
                if e.get("parentUuid") and e["parentUuid"] not in by_uuid]

    # Walk chain from latest entry
    last = next((e["uuid"] for e in reversed(entries) if e.get("uuid")), None)
    chain, current, visited = 0, last, set()
    while current and current not in visited:
        visited.add(current)
        obj = by_uuid.get(current)
        if not obj: break
        chain += 1
        current = obj.get("parentUuid")

    return len(by_uuid), len(dangling), chain

if __name__ == "__main__":
    d = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
    files = sorted(d.glob("*.jsonl"), key=lambda p: p.stat().st_size, reverse=True)
    affected = 0
    print(f"{'session':>14} {'size':>10} {'w/uuid':>7} {'dangle':>7} {'chain':>7} {'reach%':>7}")
    print("-" * 60)
    for p in files:
        if p.stat().st_size < 5000: continue
        wu, dg, ch = diagnose(p)
        if dg > 0:
            affected += 1
            pct = (ch / wu * 100) if wu else 0
            print(f"{p.stem[:14]:>14} {p.stat().st_size:>10,} {wu:>7} {dg:>7} {ch:>7} {pct:>6.1f}%")
    print(f"\n{affected} of {len(files)} sessions have dangling parentUuid refs")

Sample output from my environment (213 affected sessions):

       session       size   w/uuid  dangle   chain  reach%
------------------------------------------------------------
  f2929101-147 481,045,312      139       8       2    1.4%
  53eae523-807 53,093,787      254      35       3    1.2%
  946c4893-494 14,299,252     5783      22     301    5.2%
  1fa94892-324  9,860,472     2021      79       7    0.3%
  46c1e03f-4f3  7,065,886     2612    1031       3    0.1%

213 of 230 sessions have dangling parentUuid refs

Expected behavior

Every parentUuid in the JSONL should resolve to a UUID that exists in the same file. Messages sent to the API (system injections, hook outputs, retry state) should either:

  1. Be persisted to the JSONL, or
  2. Have their child entry's parentUuid reference a persisted entry instead

Environment

  • Claude Code 2.1.92 (Homebrew cask, Mach-O arm64)
  • macOS, Apple Silicon
  • Hooks configured (UserPromptSubmit, PreToolUse, PostToolUse, etc.)
  • Auto-compact disabled
  • Model: opus (1M context)

Notes

  • The issue is independent of /compact — no compact/summary markers exist in the affected files.
  • The chain breaks happen mid-session (seconds apart), not at session boundaries.
  • The dangling system entries consistently show ECONNRESET API errors, suggesting the retry path doesn't properly maintain the persisted chain.
  • Sessions without hooks may be less affected, but this is untested — the ECONNRESET retry entries suggest hooks are not the only cause.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Every parentUuid in the JSONL should resolve to a UUID that exists in the same file. Messages sent to the API (system injections, hook outputs, retry state) should either:

  1. Be persisted to the JSONL, or
  2. Have their child entry's parentUuid reference a persisted entry instead

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Resume loses most context: parentUuid chain breaks on unpersisted messages