Repository navigation
[Improve] Reduce chat memory retained during long sessions - #2217
Conversation
…y-optimization-0lkgswz0njjgl # Conflicts: # webview-ui/src/__tests__/App.spec.tsx # webview-ui/src/context/ExtensionStateContext.tsx # webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the
|
|
No new code issues found. See session Re-reviewed the new commit 2ff4aa6 ("Address webview memory optimization review feedback") on top of the previously reviewed 5ba6d60:
CI at this head: compile, codecov patch checks (including webview-patch), knip, translations, and VSIX build pass; remaining unit/visual/e2e jobs were still running at review time. Previously reviewed at 5ba6d60 — string interning ( Reviewed 31e260a |
Review statusThis PR was opened by an automated account. A human maintainer must verify the change intent, provenance, and validation before merging. Current step: The required review sequence passed. Remaining merge requirements apply. Review-state labels are managed by this workflow; do not edit them manually. |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…ites and retake the deleted cases Resolves the five open CodeRabbit threads on Zoo-Code-Org#1560 at 8cc8f33. Four are teardown in the specs this unit added; one is a behaviour change confined to the public API round trip. - extension/api.ts: setConfiguration drops viewStates from the settings payload before the forward write, and from the snapshot the rollback replays. The map is a snapshot of every view's selection: writing a caller's copy forward pins views this call does not own, and replaying the getter's copy on failure rolls them back to where this call found them. Both consult isNonPortableSettingsKey, the list the export schema above and the import path already use, which Zoo-Code-Org#2226 adds at its head (e9d0a2a) for the read side of the same round trip - that PR filters what an external caller may read, this one filters what it may write. getConfiguration and ContextProxy.getValues keep the key: the host's own consumers still need viewStates. - core/config/ContextProxy.ts: declares NON_PORTABLE_SETTINGS_KEYS and exports isNonPortableSettingsKey beside the export schema, so the two PRs share one rule instead of each keeping its own copy of the key list. - ClineProvider.spec: the mode-switch timeout test brackets its flow in try/finally so a failed assertion cannot leave the gate unresolved, the prototype spy installed, or the timers faked for the rest of the file. - webviewMessageHandler.spec: the sign-out suite restores the three fixture members its tests reassign. Measured control: with the assignments unrestored the file is still 176/176, so the leak is latent rather than failing - vi.clearAllMocks undoes call history, never a property assignment, and mockClineProvider.contextProxy alone is read 28 times by the eight describes that follow. - ClineProvider.sticky-mode.spec: the three temporary-id providers are disposed in finally, and the third restores mockContext.globalState.update there before disposing, so a failing assertion cannot leave the rejecting mock installed for the teardown below it. - webview-ui ExtensionStateContext.spec.tsx: restored the 13 cache-interning and task-identity cases main carries (from upstream c4ed96c, Zoo-Code-Org#2217) with the expectMessages, InternSpy and cacheFor scaffolding they need and main's afterEach(vi.restoreAllMocks). They came back with the merge of main; our scaffolding cleanup (fda1600) removed them without moving them anywhere, and webview-ui/src/utils/__tests__/stringCache.spec.ts covers the class, not this provider wiring. Verified: src tsc --noEmit exit 0; webview-ui tsc --noEmit exit 0; eslint --max-warnings=0 clean on every touched file with src/eslint-suppressions.json byte-for-byte unchanged; prettier clean. 856 tests green across api-configuration (17), api-set-configuration (1), ContextProxy (64), ClineProvider (448), sticky-mode (46), sticky-profile (23), apiHandlerRebuild (40), parallelMode (41) and webviewMessageHandler (176); webview-ui ExtensionStateContext 43/43. Test (negative control for the API filter): with neither filter in place the new case fails with `expected { Object (currentApiConfigName, modeApiConfigs, ...) } to not have property "viewStates"` while the other 16 cases in the file pass.
What changed
Why
State broadcasts repeatedly deserialize message and history strings. Sharing equal strings reduces retained allocations within a transcript and across successive snapshots. The cache limits bound its own retention; clearing a cache does not guarantee that other UI references release the same messages.
Heap measurements
Measured in real VS Code 1.100.0 under Xvfb using CDP forced GC on the Zoo Code webview renderer. Each workload loads a deterministic saved transcript of 602 UI messages, including 300 command/output pairs with 16.4–16.9 KiB outputs, then switches to a small task. A full-state refresh after hydration exercises sharing across broadcasts.
Three fresh measurement sessions per workload and variant produced 12 successful samples. The extension host, dependencies and styles are held constant; both webview builds use production minification without source maps. External downloads are blocked during the workload, and seed tasks use a local mock model.
Median loaded renderer heap:
Base webview modules:
97228c7ca9b032e5fe466e57e24fe72fbf405192. Candidate:b9a6ba03cb93c0f92f06cfb00c82ae7d488db7cb.These are historical measurements of the recorded revisions and have not been rerun for the review fixes. Repeated outputs benefit from cache interning; unchanged strings are also shared across snapshots after FIFO eviction, which helps the unique workload. The comparison does not isolate each mechanism’s contribution.
These replace the earlier standalone synthetic 8.6 MiB estimate. The new measurements exercise the real extension/webview path with generated saved-task data; they are not an organic user session and do not measure CPU overhead, image-rich messages, continuously streamed execution, extension-host heap, or process RSS.
Task-switch finding
All six candidate post-switch snapshots contain zero old output strings in the StringCache maps. Old output strings remain reachable elsewhere in both base and candidate. An inspected strong retaining path runs through React props and
onJumpToPreviousCheckpointcallback closure contexts into an old message array.Cache cleanup is confirmed, but complete task-memory release is not. Investigating that existing callback retention remains a follow-up; these snapshots do not establish indefinite growth. Chat behavior and appearance are unchanged, and no forced remounts are introduced.
Validation and artifacts
apps/vscode-e2e/src/heap/; raw snapshots, measurements and retaining-path evidence are gitignored underapps/vscode-e2e/heap-results/.