From harness-claude
Analyzes V8 heap snapshots in Chrome DevTools and Node.js using Summary, Comparison, Containment, Dominator views and 3-snapshot technique to identify memory leaks and high usage.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Master heap snapshot analysis — Summary, Comparison, Containment, and Dominator views — to precisely identify what objects consume memory, why they are retained, and how to reclaim leaked memory using the 3-snapshot technique and allocation tracking.
Diagnoses and resolves memory leaks in JavaScript/Node.js apps using memlab, heap snapshots, and comparison scripts. Useful for high memory usage, OOM errors, or leak analysis.
Identifies, diagnoses, and prevents 5 classic JavaScript memory leak patterns—detached DOM trees, forgotten event listeners, closures over large scopes, forgotten timers, global accumulation—using WeakRef, WeakMap, heap analysis. For growing browser/Node.js memory.
Detects and fixes memory leaks using heap snapshots, profiling tools, and best practices in Node.js and Python apps. Use for memory growth, OOM errors, or usage optimization.
Share bugs, ideas, or general feedback.
Master heap snapshot analysis — Summary, Comparison, Containment, and Dominator views — to precisely identify what objects consume memory, why they are retained, and how to reclaim leaked memory using the 3-snapshot technique and allocation tracking.
--heap-prof or v8.writeHeapSnapshot() is neededTake a heap snapshot. In Chrome DevTools Memory panel, select "Heap snapshot" and click "Take snapshot." The snapshot captures every object on the V8 heap with its size, type, and references. Note: taking a snapshot triggers a full GC first, so only live objects appear.
Understand the four views:
Understand shallow vs retained size:
Example: a 56-byte Map object (shallow) that holds 10,000 entries with 1KB values each has a retained size of ~10MB.
Execute the 3-snapshot technique:
1. Load page, wait for stabilization → Take Snapshot 1 (baseline)
2. Perform suspected leaking action (navigate, open dialog, etc.)
3. Undo the action (navigate back, close dialog)
4. Force GC (click trash can icon) → Take Snapshot 2
5. Repeat step 2-3
6. Force GC → Take Snapshot 3
7. In Snapshot 3, switch to Comparison view, compare against Snapshot 2
8. Filter by "Objects allocated between Snapshot 2 and Snapshot 3"
9. Objects that appear here survived two rounds → these are leaking
Read retaining paths. Select an object in the snapshot. The "Retainers" panel at the bottom shows every path from a GC root to this object. The shortest path is usually the most informative:
system / Context → () @123456 → map property → Object @789012 (your leaked object)
Each arrow represents a reference. To fix the leak, break one link in the chain.
Use allocation tracking for time-based analysis. Instead of a static snapshot, use "Allocation instrumentation on timeline" in the Memory panel. This records every allocation over time with call stacks. Blue bars are allocations that survived, gray bars are collected. Focus on the blue bars — these are retained allocations that may be leaking.
Profile Node.js heaps:
// Take a heap snapshot programmatically
const v8 = require('v8');
const fs = require('fs');
const snapshotStream = v8.writeHeapSnapshot();
console.log('Heap snapshot written to:', snapshotStream);
// Or use --heap-prof for allocation profiling
// node --heap-prof --heap-prof-interval=512 server.js
// Produces .heapprofile files loadable in Chrome DevTools
Use allocation sampling for low-overhead production profiling:
// Start sampling (low overhead, suitable for production)
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('HeapProfiler.startSampling');
// ... run workload ...
session.post('HeapProfiler.stopSampling', (err, result) => {
fs.writeFileSync('heap-profile.heapprofile', JSON.stringify(result.profile));
});
The Summary view groups objects by constructor name. Key columns:
| Column | Meaning |
|---|---|
| Constructor | The object type (e.g., Array, Object, HTMLDivElement, (closure)) |
| Distance | Number of references from GC root to object (lower = more directly referenced) |
| Shallow Size | Memory of the object itself (bytes) |
| Retained Size | Memory freed if this object were GC'd (bytes) |
Sort by "Retained Size" descending to find the biggest memory consumers. Look for unexpectedly large retained sizes on objects you expect to be small.
A team used the 3-snapshot comparison to isolate a 2MB/navigation leak in an SPA:
Comparing Snapshot 3 vs Snapshot 2: 2,400 new HTMLDivElement objects with retaining paths through a module-level variable declared as const elementCache = new WeakMap(). Investigation revealed a typo — the actual code was const elementCache = new Map() (regular Map, not WeakMap). DOM elements used as Map keys were strongly referenced, preventing GC of the entire detached subtree.
Fix: changed Map to WeakMap. Leak eliminated — heap stabilized at 52MB across navigations.
A Node.js service used --heap-prof with --heap-prof-interval=512 to sample production heap allocations at 512KB intervals. The resulting .heapprofile file was loaded in Chrome DevTools.
Analysis revealed that 40% of all allocations came from Buffer.from(JSON.stringify(logEntry)) in a logging middleware, called on every HTTP request. Each request created a 2-4KB Buffer that was immediately written to a log stream and discarded.
Fix: implemented a Buffer pool that reused pre-allocated Buffers for log serialization. Allocation rate from logging dropped by 90%, reducing Scavenge frequency from every 200ms to every 2 seconds.
Taking heap snapshots in production without understanding the pause. Taking a snapshot triggers a full GC and serialization. For a 2GB heap, this can pause the process for 1-10 seconds. In production, use allocation sampling (low overhead) instead of full snapshots.
Comparing snapshots across page reloads. Each page load creates a fresh V8 heap with new object IDs. Comparing snapshots from different page loads shows everything as "new" — the comparison is meaningless. Always compare within the same session.
Ignoring "system" and "compiled code" entries. These represent V8 internal structures and JIT-compiled code. They are not application memory leaks. Focus on application-level constructors (your classes, DOM elements, closures, strings, arrays).
Using shallow size to prioritize investigation. A small object (56 bytes shallow) can retain 100MB if it is the sole reference to a large object graph. Always sort by retained size when looking for leaks. Shallow size is only useful for understanding per-object overhead.
Not forcing GC before taking snapshots. Without forcing GC, dead objects appear in the snapshot alongside live ones, making analysis confusing. Click the trash can icon (or call gc() in Node.js with --expose-gc) before each snapshot.
--heap-prof documentation — https://nodejs.org/api/cli.html#--heap-prof