Heap snapshot diagnostics (memory bloat investigation via CLI)
A CLI endpoint that captures a heap snapshot of the renderer, parses it, and returns a small summary of where the memory is going — the supported way to answer "Omniscio is using 2 GB of RAM, where is it going?" without opening DevTools.
What it is
POST /diagnostics/heap-snapshot is a CLI endpoint on Omniscio's local control server (port 19519) that captures a V8 heap snapshot of the renderer process, parses it, and returns a small JSON summary describing where the memory is going. It's the supported way for an agent — Claude Code, the in-app chat, an external tool reading ~/.amc/cli-token — to investigate "Omniscio is using 2 GB of RAM, where is it going?" without the user having to open DevTools.
Why this exists. Omniscio strips the Windows menu accelerators (mainWindow.removeMenu()), so Ctrl+Shift+I does not open DevTools. The standard developer workaround — launching with --remote-debugging-port — is forbidden by the project's security postmortem (it would expose an authenticated debugger socket on every Omniscio start). This endpoint lets agents drive a memory diagnosis programmatically while keeping Omniscio's debugger surface closed.
Where to find it
How to use it
Calling the endpoint
TOKEN=$(cat ~/.amc/cli-token)
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:19519/diagnostics/heap-snapshot
A successful response looks like:
{
"ok": true,
"snapshotBytes": 312654321,
"summary": {
"totals": {
"nodeCount": 8421337,
"edgeCount": 31845121,
"totalSelfSize": 2147483648
},
"byNodeType": [
{ "type": "string", "count": 5132904, "totalSelfSize": 1612345678 },
{ "type": "object", "count": 1842031, "totalSelfSize": 412345122 },
{ "type": "array", "count": 421873, "totalSelfSize": 98123455 }
],
"topClassNames": [
{ "name": "system / Module", "count": 8123, "totalSelfSize": 142312345 },
{ "name": "HTMLDivElement", "count": 142003, "totalSelfSize": 84123887 }
],
"largeStrings": {
"count": 1247,
"totalSize": 783451123,
"categories": { "dataImage": 612345128, "dataOther": 41205555, "generic": 129900440 },
"samples": [{ "size": 4823472, "preview": "data:image/png;base64,iVBORw0KGgoAAAANSU…" }]
}
}
}
Reading the summary
totals.totalSelfSizeis what to compare against the OS reported renderer RSS. It's the sum of every node'sself_sizein bytes — close to JS-reachable memory, not counting V8 internals or native buffers.byNodeTypeis your first triage cut. Ifstringdominates, you're looking at retained text/base64/buffers; ifobjectdominates, you're looking at retained DOM or app objects; ifcodedominates, you have many compiled functions resident (rare).topClassNames(capped at 25 bytotalSelfSize) names the worst object retainers. Names are pulled from the V8stringstable for non-string-type nodes — for plain objects this is the constructor name (HTMLDivElement,Map, custom classes, etc.).largeStringsisolates the >1 KB string nodes and splits them intodataImage(base64-encodeddata:image/...),dataOther(otherdata:URLs — Office docs, blobs), andgeneric(everything else — long JSON blobs, pasted text, agent message buffers). The 10 largest are returned assampleswith truncated previews so you can sanity-check what's pinned.
Optional behavior
?keep=true— the captured.heapsnapshotfile is normally deleted after parsing to save disk. Withkeep=true, the response includessnapshotPathso you (or the user) can later open the file in DevTools' Memory panel for a deeper retention-path walk.- A second POST while the first is still in-flight returns 409 with
error: "snapshot capture already in flight". Capture is single-flight by design — running two concurrenttakeHeapSnapshotcalls would double the transient memory pressure.
How it behaves
Cost and limits
- Time: ~2–6 seconds end-to-end on a 2 GB renderer. The capture itself is fast; reading and parsing the resulting JSON file is the slow part.
- Disk: the snapshot file is 200–500 MB for a typical renderer. It is written under
<userData>/heap-snapshots/<ISO-timestamp>.heapsnapshotand deleted unlesskeep=true. - Main-process memory: parsing the JSON briefly inflates the main process by ~2–3× the snapshot size. Omniscio's main is normally ~200–400 MB resident; expect a transient spike to ~1.5 GB while parsing a 500 MB snapshot. This is acceptable for a user-triggered diagnostic but is the reason capture is single-flight and rate-limited.
- Rate limit: shares the 19519 server's global mutation cap (10/min). Combined with single-flight, the effective cap is one capture every few seconds.
- Auth: the full-trust
~/.amc/cli-tokenis required. That is stricter than/projects,/sessionsand the search routes, which also answer a scoped agent-session token; a scoped token is refused here.
When to use it
- "Omniscio's RSS is climbing past 2 GB and I want to know what's pinned." Run the endpoint, look at
byNodeTypefirst, then drill intotopClassNamesorlargeStrings.samplesbased on which dominates. - "I think feature X retains image data after the user navigates away." Capture a snapshot before opening X, capture another after closing it, compare
largeStrings.categories.dataImageandtopClassNamesdeltas. - "I want to verify a memory fix actually freed memory." Capture before and after —
totals.totalSelfSizeand the per-class entry for the fixed component should both drop.
When NOT to use it
- Don't poll it on a timer. Each capture costs disk + transient RAM. Use it when you have a specific question, not as a heartbeat.
- Don't use it to inspect non-renderer processes. The endpoint snapshots
mainWindow.webContentsonly; child processes (CLI spawns,mempalace-server) are out of scope. - Don't ship code that depends on the response shape long-term — the
summaryshape is a debug aid and may evolve as the parser learns to extract more useful signals.
Related
- Session memory paging — what Omniscio does when a session memory grows.
- Resources diagnostic panel — the panel that reads this alongside everything else.
- Logs and debugging — where the logs live when you need the raw detail.
Last verified 2026-10-06