Resume fails silently on large session files (100MB+) due to full-file parsing and silent error swallowing

Resolved 💬 2 comments Opened Feb 5, 2026 by mvbmir Closed Feb 5, 2026

Bug Description

/resume effectively breaks for sessions that spawned many subagents. A session with only 14 messages in its conversation chain accumulated a 102.7MB JSONL file (35,500 lines, 404 subagents) because all subagent progress/messages are written to the main session file. Resuming this session takes ~14 seconds and ~720MB RAM, and the interactive /resume picker can't display the session's custom title.

Steps to Reproduce

  1. Use a session that spawns many subagents (e.g., parallel ticket work with 400+ subagents)
  2. The session JSONL file grows to 100MB+ even though the main conversation is only ~14 messages
  3. Try /resume in the TUI — the session appears with an unhelpful firstPrompt instead of its custom title
  4. Select it — it hangs for 14+ seconds while parsing 102MB of data to extract 14 messages
  5. If memory is tight, the catch {} in s1H() silently swallows the error and returns "No conversation found"

Root Cause Analysis

Traced through the minified source (v2.1.31). The resume flow has several issues:

1. s1H() reads the entire JSONL file into memory

// s1H() at offset ~113295441
async function s1H(H) {
    let messages = new Map();
    try {
        let C = await Kz.readFile(H);       // Reads entire 102MB into buffer
        let Y = iHH(C);                      // Parses all 35,500 JSON lines
        for (let w of Y) {
            messages.set(w.uuid, w);          // Stores 34,830 entries in Map
        }
    } catch {}                                // ← Silent catch swallows ALL errors
    return { messages };
}

Measured impact:

  • Single parse: 624MB RSS, ~600ms
  • Double parse (see below): 720MB RSS, ~1.2s

2. b2$() reads the file TWICE

async function b2$(H) {
    let $ = await IW().getLastLog(H);  // Calls GbA(H) → s1H() — READ #1
    // ...
    let { summaries, customTitles } = await GbA(H);  // s1H() again — READ #2
    // ...
}

Both calls parse the entire 102MB file independently. The second read is just for metadata (summaries, titles) that could be extracted from the first read.

3. Custom title unreachable by 16KB metadata window

RY1() only reads the first and last 16KB of the file to extract metadata for the session picker. For this 102MB file:

  • customTitle entry is at line 22,501 (69MB from start, 33MB from end)
  • Neither the first nor last 16KB window can reach it
  • The picker shows "[Request interrupted by user for tool use]" instead of "Portal Facade PR Chain"

4. Silent error swallowing

The bare catch {} in s1H() means if parsing fails (OOM, corruption, etc.):

  • messages.size === 0
  • getLastLog() returns null
  • b2$() returns null
  • User sees "No conversation found" with no indication of WHY

Measured Impact

| Metric | This session | Normal session |
|--------|-------------|----------------|
| JSONL file size | 102.7 MB | 0.1 – 5 MB |
| Lines to parse | 35,500 | 100 – 2,000 |
| Entries stored in memory | 34,830 | ~200 |
| Actual conversation chain | 14 messages | ~14 messages |
| RSS to load | 720 MB | ~50 MB |
| Time to resume | 14 seconds | < 1 second |

The file is massive because 404 subagent sessions had their progress entries written into the main JSONL.

Suggested Fixes

  1. Use the sessions-index.json for the picker — it already has customTitle, summary, firstPrompt, gitBranch, prNumber, etc. No need to re-extract from raw JSONL files
  2. Stream/index the JSONL — instead of readFile() + full parse, build an index or stream from the end to find the leaf message, then traverse the parent chain
  3. Don't read the file twiceb2$() should reuse the result from the first s1H() call
  4. Log errors instead of swallowing — replace catch {} with catch (e) { log(e) } so users know WHY a session can't be loaded
  5. Append metadata near the end — write customTitle, tag, and summary entries at the tail of the file (or duplicate them there) so the 16KB window can find them
  6. Consider separate files for subagent data — subagent progress entries already go to subagents/agent-*.jsonl, but their messages still bloat the main file

Environment

  • Claude Code version: 2.1.31
  • Platform: Linux (CachyOS / Arch)
  • Node.js: v25.4.0

View original on GitHub ↗

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