chat: recover from V8 max-string-length when persisting session state#319183
Merged
Conversation
JSON.stringify on the full session state (every 1024 mutations during compaction) could exceed V8's ~512 MiB max string length when extensions stored very large content in IChatAgentResult.metadata.toolCallResults (notably Copilot's read_page tool can produce ~60 MiB browser dumps). The thrown RangeError propagated up and lost the entire chat session. Wrap the JSON.stringify(entry) calls in ObjectMutationLog with a catch for RangeError that retries with a replacer truncating strings over 1 MiB and capping total at 100 MiB. The common path is a single JSON.stringify with zero overhead; only failures pay the recovery cost. Generic across the whole schema, so any future field that grows is also protected. Fixes #308843. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (Written by Copilot)
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens chat session persistence against V8’s max string length limit by adding a fallback serialization path in the chat session mutation log, preventing entire sessions from being lost when a compaction write would otherwise throw RangeError: Invalid string length.
Changes:
- Added
stringifyEntryWithFallbackto retryJSON.stringifywith a truncating replacer afterRangeError. - Introduced a stateful truncating replacer that caps individual strings and enforces an overall serialization budget on retry.
- Added unit tests covering truncation behavior and the RangeError retry path.
Show a summary per file
| File | Description |
|---|---|
src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts |
Wraps log entry serialization with a RangeError recovery path and adds truncation helpers/constants. |
src/vs/workbench/contrib/chat/test/common/model/chatSessionOperationLog.test.ts |
Adds tests validating truncation and fallback stringify behavior. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 2
Copilot reviewer noted: - Constants were named _BYTES but track string.length (UTF-16 code units), and the 'guaranteed to succeed' claim ignored JSON escaping and keys. Renamed to _CHARS and clarified that the budget is approximate. - makeTruncatingReplacer checked total >= maxTotalChars *before* counting the current string, so one extra under-cap string could overshoot the cap. Now projects (total + val.length + 2) and emits the truncation marker if it would exceed the budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (Written by Copilot)
bhavyaus
approved these changes
Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #308843.
Problem
JSON.stringifyof the full chat session state could throwRangeError: Invalid string lengthduring compaction, losing the entire chat session. The session-store mutation log compacts every 1024 entries by writing a singleInitialentry containing the full serialized state; with multiple large tool result values stored underIChatAgentResult.metadata.toolCallResults(notably Copilot'sread_pagetool, whose results can be ~60 MiB each), the combined JSON exceeded V8's ~512 MiB max string length.Field forensics in the issue showed one tool result alone at ~61 MiB and a JSONL log file at ~640 MiB with one line at ~503 MiB before serialization eventually failed.
Fix
Wrap the three
JSON.stringify(entry)call sites inObjectMutationLogwithstringifyEntryWithFallback, which:JSON.stringify— zero overhead.RangeError, retry with a truncating replacer that caps individual strings at 1 MiB and the total tracked size at 100 MiB. Oversized values are replaced with a clear marker (e.g.[VS Code: value truncated for persistence; original 314572800 chars]).RangeErrorexceptions (circular refs, etc.) propagate unchanged.The fix lives at the engine level rather than in the chat schema, so:
result.ChatModelare unchanged.Copilot's prompt path already replaces tool results > 8 KiB with on-disk file references when building prompts, so a truncated persisted copy has no functional impact on conversation resume — only the unbounded "for the record" copy is affected.
Notes for reviewers
toJSON()hook to trigger a realRangeErrormid-stringify without requiring 300+ MiB allocations — fast (<10 ms) and exercises the actual catch+retry path. I also verified end-to-end against an actual V8 RangeError locally (allocating a 300 MiB string referenced twice in an object) and confirmed full recovery throughcreateInitial+readround-trip.(Written by Copilot)