5 MB precompact-skip threshold strips pre-boundary content from chain walker, hides scrollback
Affected: anthropic.claude-code 2.1.126 (Antigravity / VSCode extension). The bug is in the
shared sessionStorage loader and applies to any consumer that calls loadSessionFile or its
equivalent — including the VSCode chat panel, list rendering, and --resume rendering for files
≥ 5 MB.
Symptom
Sessions whose JSONL exceeds 5 MB lose all pre-most-recent-compact-boundary visibility in
both the VSCode chat panel and the CLI --resume render. Scrolling back stops at the most
recent compact_boundary system message regardless of whether the chain walker would otherwise
follow it (i.e., independent of the [#48937 secondary] / [#46603] / Patch-D-stylelogicalParentUuid fallbacks).
The pre-boundary content is on disk and well-formed — it's just never parsed into the messages
array that the chain walker sees.
Root cause
src/utils/sessionStoragePortable.ts:480 defines:
export const SKIP_PRECOMPACT_THRESHOLD = 5 * 1024 * 1024
Then in src/utils/sessionStorage.ts:3536-3556:
if (!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_PRECOMPACT_SKIP)) {
const { size } = await stat(filePath)
if (size > SKIP_PRECOMPACT_THRESHOLD) {
const scan = await readTranscriptForLoad(filePath, size)
buf = scan.postBoundaryBuf // ← drops ALL pre-boundary bytes
hasPreservedSegment = scan.hasPreservedSegment
if (scan.boundaryStartOffset > 0) {
metadataLines = await scanPreBoundaryMetadata(filePath, scan.boundaryStartOffset)
}
}
}
buf ??= await readFile(filePath)
When size > 5 MB, the loader returns only the post-most-recent-compact_boundary portion
of the file. Pre-boundary bytes never reach the JSONL parser, so the resulting messages array
that downstream consumers (chain walker, fork chooser, rewind list, render) operate on
contains only post-boundary entries. Pre-boundary metadata is recovered as a separate
metadata-only pass for things like agent-settings, but the actual user/assistant chain
messages are lost.
The justification in the source (paraphrased from comments at sessionStorage.ts:3525-3531):
Large sessions (>5 MB) almost always have compact boundaries — they got big because of many turns triggering auto-compact. Pre-boundary metadata is recovered via a cheap byte-level scan.
This reasoning holds for API context construction — getMessagesAfterCompactBoundary
(src/utils/messages.ts:4643)
already slices to post-boundary independently, so the API path is bounded regardless of what
the loader returns.
It does not hold for any consumer that wants pre-boundary visibility:
- Chain walker (
buildConversationChain-equivalent) can't reach pre-boundary messages even
with parentUuid → logicalParentUuid fallback applied — those messages aren't in the map.
- Rewind UI / fork-from-message picker can only target post-boundary messages.
- Scrollback in the VSCode chat panel stops at the boundary (the topmost rendered message is
the isCompactSummary user message).
--resume's terminal scrollback (which the source claims preserves full history per
comment at messages.ts:4636-4639) is not actually full when size > 5 MB.
Reproduction
- Take any session file > 5 MB on disk that has at least one
compact_boundarysystem
message.
- Open it in VSCode/Antigravity. Scroll up.
- Observe: scrollback stops at the most recent compact summary. Prior turns (the bulk of
the session) are unreachable.
- Set
CLAUDE_CODE_DISABLE_PRECOMPACT_SKIP=1in the environment, restart VSCode. - Same session now scrolls back through the full chain.
Concrete observation
Verified empirically on an Antigravity install with extension 2.1.126:
- Session
9b5cd3cc-...(49 MB JSONL, 10355 lines) getSession()IPC call from the webview'sloadFromServerreturned 27 messages
(post-most-recent-compactSummary slice)
- After patching the bundled equivalent of the
K > Hz4 && !envTruthy(...)check to always
bypass the optimization, same call returned 1857 messages (the full in-file chain
back to the most recent stitch with cross-file logicalParentUuid).
- Cache_read on the next API turn stayed bounded at ~30 K tokens (not the ~300 K the full
pre-boundary content would imply) — confirming getMessagesAfterCompactBoundary does its
own boundary-aware slicing on the way to the API and is unaffected.
Empirically verified the API-context guard is independent of what the loader returns. The
optimization confuses two concerns:
- Bounding API context — already handled correctly downstream by
getMessagesAfterCompactBoundary.
- Bounding parse cost — the actual purpose of
SKIP_PRECOMPACT_THRESHOLD, but at the
cost of breaking everything that wants pre-boundary visibility.
Suggested fix
Two reasonable directions:
- Move the optimization to consumers that actually need bounded output, not to the
loader. Loader returns full file; getMessagesAfterCompactBoundary slices for API
consumers; chain walker / rewind list / render consumers operate on the full set and
apply their own caps if they need them.
- Or: keep the loader optimization but split into two return paths — the truncated
buffer for API construction, and a full-file path that the chat-panel render and
chain-walking consumers go through. Add a flag to opt into the full-file path.
The cheapest workaround for end users is CLAUDE_CODE_DISABLE_PRECOMPACT_SKIP=1 in the
launch environment. That should arguably be the default for any IDE-hosted use case (where
the chat panel is the primary surface and full visibility matters more than a small parse
speedup).
_The leak gave me the loader's SKIP_PRECOMPACT_THRESHOLD constant and its justifying comment at sessionStoragePortable.ts:480 — enough to name the optimization as the cause rather than speculating from the symptom. Without it, this would have been "scrollback breaks on long sessions" with no path from the symptom to the specific 5MB threshold that gates the truncation. Trace through the loader and empirical verification on a live Antigravity install done by an instance of Claude Opus 4.7, working with the user._
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗