Description
Every request made with a caller-provided AbortSignal permanently adds an abort listener to that signal.
At src/core.ts:549-550, fetchWithTimeout() calls:
if (signal) signal.addEventListener('abort', () => controller.abort());
The listener is never removed after the request settles. Reusing one signal across a batch therefore retains one internal AbortController closure per completed request.
Reproduction
import { getEventListeners } from 'node:events';
import Browserbase from '@browserbasehq/sdk';
const controller = new AbortController();
const client = new Browserbase({
apiKey: 'test',
maxRetries: 0,
fetch: async () =>
new Response('{}', {
status: 200,
headers: { 'content-type': 'application/json' },
}),
});
for (let i = 0; i < 12; i++) {
await client.get('/ok', { signal: controller.signal });
}
console.log(getEventListeners(controller.signal, 'abort').length); // 12
Expected behavior
A completed request removes its abort forwarding listener, leaving zero listeners after the loop.
Actual behavior
All 12 listeners remain attached. Retries add additional listeners because each attempt creates another controller.
Why this matters
Long-lived applications commonly share a signal across a batch of requests. Listener accumulation retains completed request state and can produce unbounded memory growth for large batches. The forwarding callback should be registered once/removed in cleanup (or use an equivalent combined-signal mechanism), with coverage for success, error, timeout, and retry paths.
Tested against @browserbasehq/sdk 2.18.0 / current main (b781bd7).
Description
Every request made with a caller-provided
AbortSignalpermanently adds anabortlistener to that signal.At
src/core.ts:549-550,fetchWithTimeout()calls:The listener is never removed after the request settles. Reusing one signal across a batch therefore retains one internal
AbortControllerclosure per completed request.Reproduction
Expected behavior
A completed request removes its abort forwarding listener, leaving zero listeners after the loop.
Actual behavior
All 12 listeners remain attached. Retries add additional listeners because each attempt creates another controller.
Why this matters
Long-lived applications commonly share a signal across a batch of requests. Listener accumulation retains completed request state and can produce unbounded memory growth for large batches. The forwarding callback should be registered once/removed in cleanup (or use an equivalent combined-signal mechanism), with coverage for success, error, timeout, and retry paths.
Tested against
@browserbasehq/sdk2.18.0 / currentmain(b781bd7).