-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Description
Preflight Checklist
- I have searched existing requests and this feature hasn't been requested yet
- This is a single feature request (not multiple features)
Problem Statement
Session .jsonl files grow without bound because compaction (/compact and auto-compact) appends a compact_boundary marker to the same file and continues writing. For long-running or frequently-resumed sessions, this creates files that exceed V8's heap limit, causing SIGABRT crashes, OOM kills, and multi-minute startup hangs.
Real data from a production session:
| Metric | Value |
|---|---|
| File size | 386 MB |
| Compaction boundaries | 142 |
| Time span | 11 days (Feb 10–21, 2026) |
| Average segment between compactions | 2.7 MB |
| Runtime memory (current) | 386 MB loaded at startup |
| Runtime memory (with rotation) | ~3 MB (current segment only) |
| Memory reduction | 99% |
This session crashed Bun with a SIGABRT after ~5 hours of runtime due to memory pressure from the accumulated JSONL.
The core issue: compaction already creates the semantic boundary (the model's context window is reset, the summary carries forward), but the file doesn't rotate. Everything before the boundary is dead weight that will never be read during normal operation — yet it's loaded into memory on every startup and resume.
Proposed Solution
Rotate the JSONL file when a compact_boundary event is written. Start a new file for the post-compaction session, linking back to the parent via metadata.
Current behavior
session-abc.jsonl:
[messages 1-500]
{type: "system", subtype: "compact_boundary", compactMetadata: {...}}
{type: "user", message: {content: "<compaction summary>"}}
[messages 501-1000]
{type: "system", subtype: "compact_boundary", compactMetadata: {...}}
{type: "user", message: {content: "<compaction summary>"}}
[messages 1001-1500]
... (file grows forever)
Proposed behavior
Option A — Segment numbering (recommended):
abc123.jsonl: (segment 0: messages 1-500, frozen after first compaction)
abc123.1.jsonl: (segment 1: boundary + summary + messages 501-1000)
abc123.2.jsonl: (segment 2: boundary + summary + messages 1001-1500)
Benefits: Session identity preserved. Latest segment found via glob(abc123*.jsonl) | sort | last. No chain-walking needed for --resume. Backward compatible (no suffix = segment 0).
Option B — New UUIDs with metadata linking:
abc123.jsonl: (original, frozen)
def456.jsonl: (segment 1, metadata: {parentSegment: "abc123", segmentIndex: 1})
ghi789.jsonl: (segment 2, metadata: {parentSegment: "def456", rootSession: "abc123"})
Benefits: Consistent with existing UUID naming. Each segment is a standalone file. Requires chain-walking for history but --resume only needs the latest.
Design details
-
On compaction: Write the
compact_boundarymarker as the last entry in the current file. Create a new JSONL file. The first entry in the new file is the compaction summary (theusermessage containing the context). Continue writing to the new file. -
Metadata in the new file's header (or a dedicated
segment_headerentry):{ "type": "system", "subtype": "segment_header", "parentFile": "abc123.jsonl", "rootSession": "abc123", "segmentIndex": 2, "timestamp": "2026-02-21T21:04:51.661Z" } -
--resumebehavior: Load only the latest segment file. The compaction summary at the start of that file already contains everything the model needs. Full history is available by walking the chain backward viaparentFileif needed. -
sessions-index.json: The index entry for the session points to the latest segment. Old segments are not indexed (they're archived, not active). -
Startup: Only the latest segment per session is scanned. Old segments are inert files on disk — no memory cost, no parsing cost, no V8 heap pressure.
-
Backward compatibility: Sessions without segments work exactly as they do today. Rotation only activates on the next compaction.
Why this is better than alternatives
| Approach | Limitation |
|---|---|
| Size-based file cap (e.g., 50MB) | Arbitrary. Doesn't align with model context boundaries. May split mid-conversation. |
| Retention policies (delete old sessions) | Destroys data. Doesn't fix root cause (single-session growth). |
| Session cleanup plugins (#26328) | After-the-fact cleanup. Crash/OOM happens before cleanup can run. |
| Streaming/lazy parsing | Reduces but doesn't eliminate O(n) cost. Significant engineering complexity. |
| SQLite storage | Major architectural change. Breaks JSONL tooling and third-party integrations. |
| Compaction-boundary rotation (this proposal) | Zero data loss. O(1) memory. Aligns with existing semantics. Minimal change. Backward compatible. |
Impact
This single change would resolve or significantly mitigate 6 open issues:
- Claude Code crashes with SIGABRT when session JSONL files exceed V8 heap limit #19025 — SIGABRT when session JSONL exceeds V8 heap (4.9GB file)
- [BUG] Session files grow unboundedly, causing OOM crash on startup ("Aborted (core dumped)") #20367 — Session files grow unboundedly, causing OOM crash (2.1GB file)
- [BUG] Memory Leak from Unbounded Session File Growth #20200 — Memory Leak from Unbounded Session File Growth
- [BUG] Claude Code hangs when accessing large session files (>50MB) #21022 — Claude Code hangs on large session files (>50MB)
- Memory leak causing V8 OOM crashes (SIGABRT) on extended sessions #18011 — Memory leak causing V8 OOM crashes on extended sessions
- [BUG] No session file retention policy - files accumulate indefinitely causing degraded startup and eventual OOM #20429 — No session file retention policy (closed as duplicate)
All report the same root cause: a single JSONL file that grows without bound. Rotation at compaction boundaries eliminates this class of problem entirely.
Use Case Example
Power users running long-lived sessions — multi-day coding sprints, resumed sessions across weeks, heavy subagent usage — hit this wall consistently.
Today's workaround: manually move large .jsonl files out of ~/.claude/projects/, which breaks --resume and loses session history.
With rotation:
- User works normally. Sessions compact as usual.
- Each compaction creates a new ~2-5MB segment file.
- Memory usage stays constant regardless of session age.
- Full history is preserved on disk for analysis, audit, or replay.
--resumeloads only the latest segment (~3MB instead of hundreds of MB).
Concrete scenario: A developer resumes the same session daily for 2 weeks. Today: 20+ compactions accumulate into a 200MB+ file that eventually crashes. With rotation: 20 small segment files, each ~5MB, latest one loaded in milliseconds.
Alternative Solutions
The current workaround is a manual extraction tool that reads compaction boundaries from the JSONL, exports summaries to a separate file, and lets users start a fresh session with their memory/environment files carrying the context forward. This works but requires manual intervention and loses the --resume chain.
Community PRs exist for adjacent problems (#27140 memory-bridge for context consolidation, #26328 session-manager for cleanup) — this proposal addresses the root cause those plugins work around.
Additional Context
- Compaction summaries are already self-contained. They include everything the model needs to continue. Pre-compaction content is archived context, not active state. Rotation makes file structure match the semantic structure that already exists.
- The
compact_boundaryentry already marks the exact rotation point. No heuristics or size thresholds needed — the system knows when to rotate. - Multi-agent sessions are the worst case. Parent sessions log all subagent output ([BUG] Session files grow unboundedly, causing OOM crash on startup ("Aborted (core dumped)") #20367), causing exponential growth. Rotation caps each segment regardless of subagent volume.
- I'm happy to contribute implementation work if the team aligns on the approach and can point me to the relevant source files.
Priority
Critical — Blocking my work
Feature Category
Performance and speed