Session JSONL files grow unbounded — need compaction/GC for dead branches and metadata bloat
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:
- Prune dead branches — Walk from the current head leaf back to root; delete all entries not on this chain
- Remove stale metadata — Drop
file-history-snapshotandprogressentries older than N hours (or move them to a separate file) - Deduplicate per-message fields — Store session-level metadata once in a header entry, not on every message
- Clean orphan nodes — Remove entries with dangling
parentUuidreferences
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
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗