Audit code for memory leaks and improper disposal across common lifecycle patterns.
Copy the install command and let the AI configure it · recommended for beginners
Please install the "memory-leak-audit" skill from askskill: 1. Download https://raw.githubusercontent.com/microsoft/vscode/main/.github/skills/memory-leak-audit/SKILL.md 2. Save it as ~/.claude/skills/memory-leak-audit/SKILL.md 3. Reload skills and tell me it's ready
Please audit this frontend code for memory leaks caused by undisposed event listeners. Focus on addDisposableListener, DOM handlers, and component teardown timing, then suggest fixes.
A review identifying leaking listeners and cleanup gaps, with concrete disposal fixes and code recommendations.
Analyze this code with lifecycle callbacks and verify whether onWillDispose, Event.once, MutableDisposable, and DisposableStore are used correctly. Find potential leak points.
A step-by-step audit explaining which disposal patterns are correct and which may cause retained objects or duplicate subscriptions.
Here is a memory leak report and related code. Help me locate the root cause, determine whether it is related to improper disposable management, and propose a minimal-change fix.
Root cause analysis, affected code locations, a fix strategy, and checks to verify the leak is resolved.
The #1 bug category in VS Code. This skill encodes the patterns that prevent and fix leaks.
Work through each check in order. A single missed pattern can cause thousands of leaked objects.
Rule: Never use raw .onload, .onclick, or addEventListener() directly. Always use addDisposableListener().
// BAD — leaks a listener every call
this.iconElement.onload = () => { ... };
// GOOD — tracked and disposable
this._register(addDisposableListener(this.iconElement, 'load', () => { ... }));
Validated by: PR #280566 — Extension icon widget leaked 185 listeners after 37 toggles.
Rule: Use Event.once() for events that should only fire once (lifecycle events, close events, first-change events).
// BAD — listener stays registered forever after first fire
model.onDidDispose(() => store.dispose());
// GOOD — auto-removes after first invocation
Event.once(model.onDidDispose)(() => store.dispose());
Validated by: PRs #285657, #285661 — Terminal lifecycle hacks replaced with Event.once().
Rule: Objects created in methods called multiple times must NOT be registered to the class this._register(). Use MutableDisposable or return IDisposable to the caller.
// BAD — every call adds another listener to the class store
startSearch() {
this._register(this.model.onResults(() => { ... }));
}
// GOOD — MutableDisposable ensures max 1 listener
private readonly _searchListener = this._register(new MutableDisposable());
startSearch() {
this._searchListener.value = this.model.onResults(() => { ... });
}
When the event should only fire once per method call, combine Event.once() with MutableDisposable — this auto-removes the listener after the first invocation while still guarding against repeated calls:
private readonly _searchListener = this._register(new MutableDisposable());
startSearch() {
this._searchListener.value = Event.once(this.model.onResults)(() => { ... });
}
Validated by: PR #283466 — Terminal find widget leaked 1 listener per search.
Rule: When creating a DisposableStore tied to a model's lifetime, register model.onWillDispose(() => store.dispose()) to the store itself.
const store = new DisposableStore();
store.add(model.onWillDispose(() => store.dispose()));
store.add(model.onDidChange(() => { ... }));
Validated by: Pattern used in chatEditingSession.ts, fileBasedRecommendations.ts, testingContentProvider.ts.
Rule: When using factory methods that create pooled objects (lists, trees), disposables must be registered to the individual item, not the pool class.
// BAD — registers to pool, never cleaned per item
createItem() {
const item = new Item();
this._register(item.onEvent(() => { ... }));
return item;
}
// GOOD — wrap with item-scoped disposal
createItem(): IDisposable & Item {
const store = new DisposableStore();
const item = new Item();
store.add(item.onEvent(() => { ... }));
return { ...item, dispose: () => store.dispose() };
}
Validated by: PR #290505 — Chat content parts CollapsibleListPool and TreePool leaked disposables.
Rule: Every test suite that creates disposable objects must call ensureNoDisposablesAreLeakedInTestSuite().
…
Generate or update chat customization files for AI coding agents.
Merge session branch changes back into the base branch cleanly.
Create and maintain screenshot test fixtures for UI components effectively.
Launch VS Code OSS in isolation for automation and multi-process debugging.
Configure and manage agents, skills, prompts, and integrations in the editor.
Investigate failed PR checks and iteratively fix CI issues faster.
Run deterministic funnel, accessibility, and LLM visibility audits with honest reports.
Audit Convex performance issues across reads, subscriptions, writes, and function limits.
Audit codebases to find semantically duplicate functions with different names or implementations.
Run chat performance benchmarks and memory leak checks for VS Code builds.
Detect lookahead bias and data leakage in ML and trading code.
Scan binary artifacts for sensitive strings and detect telemetry leak risks.