Summary
The VS Code extension's webview (renderer process) has several patterns that cause unbounded memory growth during normal usage. Given the hard ~4 GB V8 heap limit imposed by Electron's pointer compression, a 3-hour active session with moderate tool usage can approach this ceiling and trigger the "grey screen" OOM crash.
This issue documents the specific memory accumulation patterns found in the codebase, ordered by severity.
Critical Issues
1. No message/part cleanup on session switch (HIGHEST IMPACT)
Files: packages/kilo-vscode/webview-ui/src/context/session.tsx:796-831
The SolidJS store holds messages: Record<string, Message[]> and parts: Record<string, Part[]>. When the user switches sessions, handleMessagesLoaded() adds the new session's messages+parts to the store but never removes the old session's data. Only handleSessionDeleted() (line 1135) does proper cleanup.
If a user visits 10 different sessions during a 3-hour window, all 10 sessions' full message histories accumulate in the webview's JS heap permanently. A single heavy session with 100+ tool calls can easily be 5-20 MB of retained JSON. Across many sessions this compounds fast.
Additionally, handleSessionsLoaded() (line 1115) reconciles the sessions index (removing stale entries) but leaves orphaned messages and parts for those removed sessions.
2. No message list virtualization
File: packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx:142-159
<For each={userMessages()}>
{(msg, idx) => <VscodeSessionTurn ... />}
</For>
Every message in the current session is rendered as a full DOM subtree simultaneously. No virtual scrolling, no windowing, no "load more" pagination. The desktop app (packages/app/) has turnInit = 10 / turnBatch = 8 windowing — the VS Code extension does not.
In a long session (3 hours of active coding), you can accumulate hundreds of messages. Each VscodeSessionTurn renders the user message, all assistant messages, and all tool parts with their output. The DOM tree grows without bound.
3. SSE message.part.updated publishes unstripped full parts
File: packages/opencode/src/session/index.ts:830-833
Bus.publish(MessageV2.Event.PartUpdated, {
part: structuredClone(part),
})
The structuredClone(part) contains the full raw part including metadata.filediff.before/after (full file contents for edit tools). stripPartMetadata() is only applied on REST API reads, NOT on Bus/SSE events. The extension's KiloProvider does apply slimPart() before postMessage (line 2901-2906), but the full payload still traverses the SSE pipe and gets parsed in the extension host before trimming.
4. session.diff SSE events carry full file before/after content
Files: packages/opencode/src/session/summary.ts:94-97, packages/opencode/src/snapshot/index.ts:278-286
Snapshot.FileDiff includes before: string and after: string — complete file contents. Published raw via Bus. If a session edits 10 files averaging 50KB each, that's ~1MB per diff event, firing after every turn.
Medium Issues
5. slimPart only covers 5 of 15+ tool types
File: packages/kilo-vscode/src/kilo-provider/slim-metadata.ts:142-148
Only edit, apply_patch, multiedit, write, and bash are slimmed. Notably missing: webfetch (can store entire web pages), websearch, codesearch, read, glob, grep, task, and all MCP tools.
6. trackedSessionIds grows monotonically
File: packages/kilo-vscode/src/KiloProvider.ts:148
Every session opened and every child session spawned via the task tool is added. Only removed on explicit session deletion. This broadens the SSE event filter over time, causing unnecessary postMessage forwarding.
7. imageDrafts Map holds base64 data URLs indefinitely
File: packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx:36
Module-level Map stores full base64 data URLs per session draft. A single screenshot can be 1-5 MB as base64. Never cleaned on session deletion.
8. messageSessionIdsByMessageId Map never cleaned per-session
File: packages/kilo-vscode/src/services/cli-backend/connection-service.ts:52
Grows with every message across all sessions. Only cleared on dispose().
Recommendations
Immediate wins (low-effort, high-impact):
- Evict old sessions from the webview store on session switch. Delete
store.messages[oldSessionID] and associated store.parts entries. Turns memory from O(sessions visited) to O(1).
- Clean up orphaned messages/parts in
handleSessionsLoaded. When sessions are removed from the index, also delete their messages and parts.
- Add
webfetch, websearch, codesearch, and MCP tool slimming to slim-metadata.ts. Cap their output the same way bash is capped.
- Clean
imageDrafts, drafts, reviewDrafts on session deletion.
Medium-effort improvements:
- Implement message list virtualization similar to the desktop app's
turnInit/turnBatch approach.
- Apply
stripPartMetadata server-side on SSE events, not just REST reads.
- Strip
before/after from session.diff events before SSE publishing.
- Prune
trackedSessionIds when switching sessions.
Longer-term:
- Server-side SSE session filtering — currently all events broadcast to all clients.
- Cursor-based pagination for
session.messages() — currently no offset/cursor, always loads from beginning.
Context
- Electron's V8 pointer compression imposes a hard ~4 GB heap limit per process
- The renderer process (which hosts extension webviews) is the process that grey-screens on OOM
- Total system RAM is irrelevant — the crash happens at the V8 heap ceiling regardless
- This affects all users but is most pronounced with heavy tool usage, long sessions, and multi-session workflows (Agent Manager)
Summary
The VS Code extension's webview (renderer process) has several patterns that cause unbounded memory growth during normal usage. Given the hard ~4 GB V8 heap limit imposed by Electron's pointer compression, a 3-hour active session with moderate tool usage can approach this ceiling and trigger the "grey screen" OOM crash.
This issue documents the specific memory accumulation patterns found in the codebase, ordered by severity.
Critical Issues
1. No message/part cleanup on session switch (HIGHEST IMPACT)
Files:
packages/kilo-vscode/webview-ui/src/context/session.tsx:796-831The SolidJS store holds
messages: Record<string, Message[]>andparts: Record<string, Part[]>. When the user switches sessions,handleMessagesLoaded()adds the new session's messages+parts to the store but never removes the old session's data. OnlyhandleSessionDeleted()(line 1135) does proper cleanup.If a user visits 10 different sessions during a 3-hour window, all 10 sessions' full message histories accumulate in the webview's JS heap permanently. A single heavy session with 100+ tool calls can easily be 5-20 MB of retained JSON. Across many sessions this compounds fast.
Additionally,
handleSessionsLoaded()(line 1115) reconciles thesessionsindex (removing stale entries) but leaves orphanedmessagesandpartsfor those removed sessions.2. No message list virtualization
File:
packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx:142-159Every message in the current session is rendered as a full DOM subtree simultaneously. No virtual scrolling, no windowing, no "load more" pagination. The desktop app (
packages/app/) hasturnInit = 10/turnBatch = 8windowing — the VS Code extension does not.In a long session (3 hours of active coding), you can accumulate hundreds of messages. Each
VscodeSessionTurnrenders the user message, all assistant messages, and all tool parts with their output. The DOM tree grows without bound.3. SSE
message.part.updatedpublishes unstripped full partsFile:
packages/opencode/src/session/index.ts:830-833The
structuredClone(part)contains the full raw part includingmetadata.filediff.before/after(full file contents for edit tools).stripPartMetadata()is only applied on REST API reads, NOT on Bus/SSE events. The extension'sKiloProviderdoes applyslimPart()beforepostMessage(line 2901-2906), but the full payload still traverses the SSE pipe and gets parsed in the extension host before trimming.4.
session.diffSSE events carry full file before/after contentFiles:
packages/opencode/src/session/summary.ts:94-97,packages/opencode/src/snapshot/index.ts:278-286Snapshot.FileDiffincludesbefore: stringandafter: string— complete file contents. Published raw via Bus. If a session edits 10 files averaging 50KB each, that's ~1MB per diff event, firing after every turn.Medium Issues
5.
slimPartonly covers 5 of 15+ tool typesFile:
packages/kilo-vscode/src/kilo-provider/slim-metadata.ts:142-148Only
edit,apply_patch,multiedit,write, andbashare slimmed. Notably missing:webfetch(can store entire web pages),websearch,codesearch,read,glob,grep,task, and all MCP tools.6.
trackedSessionIdsgrows monotonicallyFile:
packages/kilo-vscode/src/KiloProvider.ts:148Every session opened and every child session spawned via the
tasktool is added. Only removed on explicit session deletion. This broadens the SSE event filter over time, causing unnecessarypostMessageforwarding.7.
imageDraftsMap holds base64 data URLs indefinitelyFile:
packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx:36Module-level
Mapstores full base64 data URLs per session draft. A single screenshot can be 1-5 MB as base64. Never cleaned on session deletion.8.
messageSessionIdsByMessageIdMap never cleaned per-sessionFile:
packages/kilo-vscode/src/services/cli-backend/connection-service.ts:52Grows with every message across all sessions. Only cleared on
dispose().Recommendations
Immediate wins (low-effort, high-impact):
store.messages[oldSessionID]and associatedstore.partsentries. Turns memory from O(sessions visited) to O(1).handleSessionsLoaded. When sessions are removed from the index, also delete theirmessagesandparts.webfetch,websearch,codesearch, and MCP tool slimming toslim-metadata.ts. Cap their output the same waybashis capped.imageDrafts,drafts,reviewDraftson session deletion.Medium-effort improvements:
turnInit/turnBatchapproach.stripPartMetadataserver-side on SSE events, not just REST reads.before/afterfromsession.diffevents before SSE publishing.trackedSessionIdswhen switching sessions.Longer-term:
session.messages()— currently no offset/cursor, always loads from beginning.Context