Session JSONL files grow unbounded — need compaction/GC for dead branches and metadata bloat

Resolved 💬 6 comments Opened Apr 2, 2026 by Loong0x00 Closed Jun 21, 2026

Problem

Session JSONL files grow without bound and are never compacted. A single session file can reach 800MB+ (336MB main JSONL + 360MB subagents + 98MB tool-results) while containing only ~99KB of meaningful information — an 8000:1 bloat ratio.

Root causes analyzed from a real 80,321-entry session file:

1. Context collapse creates orphan trees (not pruned)
Each /compact inserts a compact_boundary with parentUuid: null, creating a new root. The old chain becomes unreachable but is never deleted. 81 compacts produced 83 root nodes and 61 disconnected trees in one file.

2. Edit-and-resend creates dead branches (not pruned)
Each edit-resend forks a new branch via parentUuid. Old branches become leaves that are never cleaned up. One session accumulated 320 leaf nodes across 259 dead branches.

3. Non-conversation entries dominate file size

user messages (tool_result):  150 MB  (46%)
file-history-snapshot:        105 MB  (32%)
assistant messages:            47 MB  (14%)
progress (discarded on load):  24 MB   (7%)

file-history-snapshot and progress entries are never useful after the session ends but persist forever.

4. Per-message metadata repetition
Every message carries version, cwd, gitBranch, slug, entrypoint, userType, sessionId — fields that rarely change within a session but are repeated 80,000 times (~15MB of pure duplication).

5. 5,262 orphan nodes — messages whose parentUuid points to non-existent UUIDs, completely unreachable from any leaf.

Impact on resume (claude -r)

The ti$() function selects the head by finding the leaf with the latest timestamp across ALL trees. With 320 leaves from 61 trees (many being compact-orphaned corpses), it can select a leaf from an old dead tree instead of the current conversation, causing resume to load stale history from weeks ago.

This is related to #37437 but the root cause is architectural: the lack of any GC/compaction mechanism.

Proposal: Session file compaction

Add a compaction pass (could run on session close, on resume, or periodically) that:

  1. Prune dead branches — Walk from the current head leaf back to root; delete all entries not on this chain
  2. Remove stale metadata — Drop file-history-snapshot and progress entries older than N hours (or move them to a separate file)
  3. Deduplicate per-message fields — Store session-level metadata once in a header entry, not on every message
  4. Clean orphan nodes — Remove entries with dangling parentUuid references

Alternatively, consider a simpler architectural fix: instead of creating a new root on compact, keep a single chain with a "start-from" marker. This eliminates the multi-tree problem entirely.

Expected improvement

In the analyzed case: 794MB → estimated <10MB after compaction, with zero information loss for the active conversation chain.

Environment

  • Claude Code 2.1.89/2.1.90
  • Session spanning 2026-02-24 to 2026-04-02 (~5 weeks continuous use)
  • 61 conversation trees, 81 context compactions, 259 dead branches

View original on GitHub ↗

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