Memory leak in dashboard frontend
Follow-up: full-memory dump analysis of two Serena-hosted WebView2 renderers
I ran a comparison between a "small" (fresh) and a "big" (~22 h old) Serena dashboard renderer on this box, using full-memory dumps captured with
procdump -ma. No dump data is attached (project code could be in the address space). Only aggregate counts and inferences below, all reproducible from a fresh install.Targets
Both processes are Serena-hosted WebView2 renderers identified by
--webview-exe-name=python.exe+tmpXXXX\EBWebViewuser-data-dir:
Process Working set Private bytes Full dump size Age at capture Fresh renderer 122 MB 97 MB 528 MB ~38 min Aged renderer 1,867 MB 2,152 MB 2,650 MB ~22.4 h String-content comparison
Extracted with Sysinternals
strings64 -a -n 20:
Metric Fresh Aged Δ String count (≥ 20 chars) 312,332 724,114 2.3× Total text bytes 22 MB 261 MB 11.9× Mean string length 74 B 370 B 5.0× The aged renderer holds ~5× longer strings on average — the signature of accumulated long JSON/log payloads rather than just more short tokens.
What actually accumulated
Top recurring long strings in the aged dump (count × exact response body):
Count Response body Endpoint (inferred) 36,884 {"last_execution":null,"status":"success"}/last_execution34,458 {"active_project":{...},"active_tools":[...],"available_contexts":[...],...}(~4 KB)/get_config_overview11,987 {"queued_executions":[],"status":"success"}/queued_task_executions(empty)~7,457 unique task_idvalues, each appearing dozens of times{"queued_executions":[{"finished_successfully":false,...,"task_id":N}],...}/queued_task_executions(non-empty)URL string counts in the aged dump (polling surface):
1222 http://127.0.0.1:24286/heartbeat 593 http://127.0.0.1:24286/dashboard/index 309 http://127.0.0.1:24286/last_execution 306 http://127.0.0.1:24286/queued_task_executions 88 http://127.0.0.1:24286/heartbeatfstatus 8 http://127.0.0.1:24286/get_config_overviewThe fresh renderer shows the same pattern at lower counts — 1,006
/last_executionbodies, 597 config blobs, 803 empty-queue bodies. The leak is present from the first few minutes of life and grows linearly.Poll-rate inference
From 36,884
/last_executionbodies over ~80,700 seconds of uptime: ~1 poll every 2.2 s for that endpoint. The config-overview endpoint shows ~1 poll every 3.8 s from the same math applied to its count. Matches the URL counts above as a sanity check.Memory math
- Directly attributable retained response bodies: ~34K × 4 KB (config blob) + ~37K × 42 B (last_execution) + ~7.5K × 280 B (task_id variants, ~5 copies each) ≈ ~150 MB of serialized JSON
- Renderer growth observed: ~1.7 GB (122 → 1,867 MB WS)
- Typical Chromium/V8 heap multiplier on parsed JSON (hidden classes, string interning, object overhead): 5–10×
- 150 MB serialized × ~10× multiplier ≈ 1.5 GB → matches the 1.7 GB growth almost exactly
The renderer appears to be parsing every polled response into the V8 heap and retaining references so the parsed objects never get garbage-collected.
What this means for this issue
The process-lifecycle leak that this issue (#1387) originally describes is the amplifier, not the sole root cause. Even a fully cleaned-up dashboard — where orphans are reliably terminated on client disconnect — would still grow at approximately 30–100 MB of renderer RAM per hour of active dashboard life, purely from JS-side retention of polled HTTP responses.
Two distinct bugs compound in the observed behavior:
Frontend retention bug (new). The dashboard's JS retains every response from at least
/last_execution,/get_config_overview, and/queued_task_executions. This is most likely a state-store / closure / DOM-append pattern in the dashboard app that never prunes. Fix is frontend-side.Process lifecycle bug (the original scope of this issue). Orphaned Serena Python processes keep their pywebview subprocesses alive, which keep polling and growing indefinitely. Fix is in the Serena agent /
_start_dashboard_viewercleanup.Suggested debugging hints for maintainers (I won't prescribe fixes)
- Open the dashboard in a plain browser (no pywebview), leave it open for 10 minutes, and check Chrome DevTools → Memory → Heap snapshot. Expect a long list of polling response objects retained under the page root.
- The polled endpoints all respond with JSON that's safe to retain nothing from (they're status queries). A simple fix would be to discard the response after updating the UI, and ensure the UI renders by replace rather than append.
- If the frontend is using a reactive framework with a state store (Vue/Svelte/React/Alpine), check whether responses are being pushed into a list-shaped state field. The duplicate-count pattern (identical strings appearing tens of thousands of times) is consistent with a growing array of historical responses.
Offer
If a Chrome DevTools heap snapshot from a fresh dashboard session would help narrow this down, I can capture one with
web_dashboard: truereenabled temporarily and share the heap-snapshot JSON (no project data, just dashboard state). Let me know.
Originally posted by @scarson in #1387
Source: oraios/serena