[BUG] VS Code: post-compact skip ignored when boundaries carry preservedSegment — every tab parses the whole transcript, tab restore after host restart OOM-loops (2.1.282)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Every tab of a long, compacted session makes the extension host parse the entire transcript, not just the part after the last compaction. That costs ≈1.1–1.2× the file size in V8 heap, inside the host shared by all tabs, whose heap Electron caps at 4 GB (pointer compression). After any extension-host restart (extension update or rollback, auto-update, crash) VS Code restores all open Claude tabs almost at once, their heaps add up, and the host crashes out of memory; the automatic restart restores them again, and the loop begins, with every Claude tab down. We recorded a crash 21 s after five tabs totalling 4.3 GiB of transcripts, none of them over 2 GiB, were restored within 13 s, and 13 host crashes in 47 minutes overall, on 2.1.282, 2.1.281 and 2.1.280. Because /compact appends instead of shrinking the file, every active session drifts toward this limit.
Cause (extension.js 2.1.280): for transcripts over 5 MiB, readSessionForHost uses a post-boundary scanner that should keep only the bytes after the last compact_boundary (threshold and call at line 419 col 7409; bundled SDK copy at line 389 col 44308). The scanner classifies a boundary as having a preserved segment when compactMetadata.preservedSegment || compactMetadata.preservedMessages is set (line 109 col 10258; SDK 326:9070), and resets its buffer only when that is false (line scan 109:11565, chunk straddle 109:11080; SDK 326:10502 and 326:10017). Every compact_boundary in our transcripts carries both preserved fields, so the reset never happens and the "post-boundary" buffer is the whole file. A line is also recognised as a boundary only if "compact_boundary" occurs within its first 256 bytes (109:11509), which depends on JSON key order.
| real transcript (MiB) | parsed by the extension | V8 heap after parse (MiB) |
|---|---|---|
| 212 | 100 % | 258 |
| 543 | 100 % | 643 |
| 874 | 100 % | 1 063 |
| 954 | 100 % | 1 141 |
| 1 803 | 100 % | 1 935 |
Measured by running the extension's own reader and parser under plain Node with a raised heap.
What Should Happen?
Opening a session loads the part after the last boundary plus the few preserved messages (their uuids are listed in the boundary), so memory is bounded by that part, not by the file. Restoring tabs after a host restart must not add up full-transcript parses, and one tab must not be able to take the host down.
Error Messages/Logs
renderer.log:
[info] Extension host (LocalProcess pid: <n>) is unresponsive.
[error] Extension host (LocalProcess pid: <n>) terminated unexpectedly. The following extensions were running: … Anthropic.claude-code …
[info] Automatically restarting the extension host.
Claude VSCode.log, right before each crash: a burst of get_session_request with "purpose":"transcript_open" for several sessions.
crashpad: one dump per crash, exception code 0xE0000008 (out of memory). Dumps are not attached (they contain process memory); available privately on request.
Steps to Reproduce
- Download the gist https://gist.github.com/BlackWhiteYellow/f44e367254db24fbe20d7fe10002ab1f.
- Generate two 30 MiB transcripts:
python gen_synthetic_transcript.py with_segments.jsonl --target-mb 30andpython gen_synthetic_transcript.py no_segments.jsonl --target-mb 30 --no-segments. They differ only in the preserved fields of the boundaries. - Run the extension's post-boundary reader on both, e.g.
readSessionForHostor the scanner behind the 5 MiB threshold. Result on 2.1.280:with_segmentskeeps 100 % with boundary start at byte 0;no_segmentskeeps 0 % with boundary start at the last boundary (~30 MiB). - In-editor effect: have several large compacted sessions open in one window (in our case five tabs totalling 4.3 GiB of transcripts), then run "Developer: Restart Extension Host". The restore parses all of them at once, and past ~4 GB of combined heap the host crashes and loops. (The gist's generator uses a fixed seed, so it produces a single session id; it is meant for steps 2–3 and for the single-file repro in #97229.)
Verification status: steps 2–3 were run with the extension's own functions under Node; step 4 was observed with real transcripts, not with synthetic ones.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.282 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Other
Additional Information
Suggested fixes:
- At a boundary carrying preserved fields, still reset the buffer and collect only the listed preserved records.
- Parse incrementally per chunk instead of accumulating the whole file into one Buffer.
- After a host restart, restore Claude tabs lazily or one at a time.
- Load transcripts outside the shared host (worker thread / child process), or cap memory per load and fail only that tab.
- Do not depend on JSON key order to recognise a boundary.
- Consider bounding transcript growth (archive pre-boundary content) or warning when a transcript passes ~1 GB.
Regression: seen on 2.1.280–2.1.282 in our logs, and 2.1.278 has the same code; it surfaced as the transcripts grew. Older versions were not tested: if the CLI started writing preserved fields on every compaction at some point, the skip may have stopped working then; we have not verified this.
Environment: Claude Code VS Code extension 2.1.282 (latest; also seen on 2.1.281 and 2.1.280; 2.1.278 has the same code); VS Code 1.139.0 (commit 2242ebbb54), Electron 43.6.0, Node 24.20.0; Windows 10 Home 22H2, build 19045.6466, 64 GB RAM. Surface: the VS Code extension's graphical panel. Not model-specific.
Related: #97229 (the >2 GiB loop; same host-crash signature), nodejs/node#66294, #23953, and #92089 (open; repeated /compact re-appends earlier history, so transcripts grow quadratically). Also #89075 (open): extension host OOM from loading the histories of all sessions for the session list. That is a different request path (session-list requests there, get_session_request with transcript_open here), but if both use the same transcript reader, the preserved-segment defect would make that path read whole files too.