memory-leak-audit
Audit code for memory leaks and disposable issues. Use when reviewing event listeners, DOM handlers, lifecycle callbacks, or fixing leak reports. Covers addDisposableListener, Event.once, MutableDisposable, DisposableStore, and onWillDispose patterns.
Security Assessment
About memory-leak-audit
A focused audit guide for finding and fixing memory leaks in the VS Code codebase, described as the project's number-one bug category. It encodes the concrete disposable and event-listener patterns that prevent leaks, so it is most useful when reviewing code that registers event listeners or DOM handlers, investigating reports of listener counts growing over time, creating objects inside repeatedly called methods, or wiring up model-lifecycle events.
The core is a six-step audit checklist, each step pairing an anti-pattern with the correct pattern and citing the VS Code pull request that validated it. Step 1 requires addDisposableListener() instead of raw onload, onclick, or addEventListener (PR #280566, where an icon widget leaked 185 listeners over 37 toggles). Step 2 uses Event.once() for events that should fire a single time, such as lifecycle and close events. Step 3 handles objects created in repeatedly called methods: they must not be registered to the class via this._register(); instead use a MutableDisposable (optionally combined with Event.once()) or return an IDisposable to the caller, guarding against one-listener-per-call growth. Step 4 ties a DisposableStore to a model's lifetime by adding model.onWillDispose(() => store.dispose()) to the store itself. Step 5 covers resource pools, where disposables must be scoped to the individual pooled item rather than the pool class. Step 6 requires ensureNoDisposablesAreLeakedInTestSuite() in every test suite that creates disposables.
A quick-reference table maps each scenario to its pattern and anti-pattern for fast lookup. The verification section explains how to confirm a fix: compare listener counts before and after repeated operations, run the test-suite leak checker, and confirm object counts stabilize rather than growing linearly. For chat-specific leaks it points to a dedicated checker run via npm run perf:chat-leak (from the chat-perf skill), which sends N messages, forces GC between each, and uses linear regression on heap and DOM samples, where a slope above 2 MB per message signals a leak.
FAQ
What is the main rule for DOM event listeners?
Never use raw .onload, .onclick, or addEventListener() directly; always wrap with addDisposableListener() registered via this._register() so the listener is tracked and disposed.
How do I avoid leaks from a method that is called repeatedly?
Do not register per-call objects with this._register(); use a MutableDisposable (which caps it at one listener) or return an IDisposable to the caller, optionally combined with Event.once().
How do I verify a leak is actually fixed?
Compare listener counts before and after repeated operations, run ensureNoDisposablesAreLeakedInTestSuite() in tests, and confirm object counts stabilize instead of growing linearly with usage.
How are chat-specific leaks detected?
Run npm run perf:chat-leak (from the chat-perf skill); it sends N messages, forces GC between each, and uses linear regression on heap and DOM samples, where a slope above 2 MB per message indicates a leak.
What should every test suite that creates disposables include?
A call to ensureNoDisposablesAreLeakedInTestSuite(), imported from the base test utils, so test disposables are tracked automatically.
Install memory-leak-audit
Quick Setup:
- Copy the skill folder to
.claude/skills/ - Claude will automatically detect and use the skill
Repository
microsoft/vscode