Summary
Two confirmed V8 Oilpan OOM crashes caused by unbounded DOM growth in #chat-history. Every message was appended as a live DOM node and never removed.
Crash modes confirmed
Mode 1 — Gradual accumulation during streaming:
A 20-round agent session built up 40+ message bubbles and tool-call DOM subtrees until the V8 Oilpan C++ heap exhausted its virtual address reservation. The GC freed 419 MB → 16 MB just before the fatal allocation — confirming it's a reservation limit, not physical RAM.
Mode 2 — Bulk session load:
selectSession() rendered the entire session history in a single synchronous loop then ran hljs.highlightElement() on every code block. A fixed-size Oilpan pool exhausted at only 78 MB, 11 minutes after startup.
[pid 13269] V8 process OOM (Oilpan: Large allocation. Ran out of reservation) — 419 MB, 52 min uptime
[pid 13699] V8 process OOM (Oilpan: Large allocation. Ran out of reservation) — 78 MB, 11 min uptime
Fix applied
Crash recovery stopgap (linux_wrapper.py, commit 564dd5c): renderProcessTerminated signal handler auto-reloads the page via setUrl() on OOM/hard crash. Fork-only (Qt wrapper).
DOM virtualization (static/js/chatHistory.js, sessions.js, index.html, style.css, commit e01ded4):
MessageWindow class — renders only the last 50 messages on load; IntersectionObserver on a top sentinel loads older batches of 25 as the user scrolls up
- Scroll position preserved via
scrollHeight delta technique
- Live pruning: caps
#chat-history at 80 DOM children; prunes oldest 20 to a height-matched spacer, restores via IntersectionObserver on scroll
- Bidirectional cap at 120 nodes
overflow-anchor: none on sentinels/spacers — prevents Chrome scroll-anchor from double-compensating programmatic scrollTop
Upstream PR staged: docs/fork/upstream/drafts/06-dom-oom-virtualization.md — awaiting screenshots before filing.
Status
Resolved on develop. Upstream contribution pending screenshots.
Summary
Two confirmed V8 Oilpan OOM crashes caused by unbounded DOM growth in
#chat-history. Every message was appended as a live DOM node and never removed.Crash modes confirmed
Mode 1 — Gradual accumulation during streaming:
A 20-round agent session built up 40+ message bubbles and tool-call DOM subtrees until the V8 Oilpan C++ heap exhausted its virtual address reservation. The GC freed 419 MB → 16 MB just before the fatal allocation — confirming it's a reservation limit, not physical RAM.
Mode 2 — Bulk session load:
selectSession()rendered the entire session history in a single synchronous loop then ranhljs.highlightElement()on every code block. A fixed-size Oilpan pool exhausted at only 78 MB, 11 minutes after startup.Fix applied
Crash recovery stopgap (
linux_wrapper.py, commit564dd5c):renderProcessTerminatedsignal handler auto-reloads the page viasetUrl()on OOM/hard crash. Fork-only (Qt wrapper).DOM virtualization (
static/js/chatHistory.js,sessions.js,index.html,style.css, commite01ded4):MessageWindowclass — renders only the last 50 messages on load;IntersectionObserveron a top sentinel loads older batches of 25 as the user scrolls upscrollHeightdelta technique#chat-historyat 80 DOM children; prunes oldest 20 to a height-matched spacer, restores viaIntersectionObserveron scrolloverflow-anchor: noneon sentinels/spacers — prevents Chrome scroll-anchor from double-compensating programmaticscrollTopUpstream PR staged:
docs/fork/upstream/drafts/06-dom-oom-virtualization.md— awaiting screenshots before filing.Status
Resolved on
develop. Upstream contribution pending screenshots.