From 9a46fe2f7bf89eb5021189343cf665cd4febef75 Mon Sep 17 00:00:00 2001 From: mukunda katta Date: Mon, 11 May 2026 15:21:46 -0700 Subject: [PATCH] Fix abort signal listener cleanup --- src/client.ts | 1 + tests/index.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/client.ts b/src/client.ts index 86130c98d8..636dc030f7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -969,6 +969,7 @@ export class OpenAI { return await this.fetch.call(undefined, url, fetchOptions); } finally { clearTimeout(timeout); + if (signal) signal.removeEventListener('abort', abort); } } diff --git a/tests/index.test.ts b/tests/index.test.ts index fc8bc2f745..a21ce2f5d5 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -323,6 +323,33 @@ describe('instantiate client', () => { expect(spy).toHaveBeenCalledTimes(1); }); + test('custom signal listener is removed after fetch succeeds', async () => { + const client = new OpenAI({ + apiKey: 'My API Key', + adminAPIKey: 'My Admin API Key', + fetch: async () => + new Response(JSON.stringify({}), { + headers: { 'Content-Type': 'application/json' }, + }), + }); + + const callerController = new AbortController(); + const requestController = new AbortController(); + const addEventListenerSpy = jest.spyOn(callerController.signal, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(callerController.signal, 'removeEventListener'); + + await client.fetchWithTimeout( + 'http://localhost:5000/foo', + { signal: callerController.signal }, + 1000, + requestController, + ); + + const abortListener = addEventListenerSpy.mock.calls.find(([event]) => event === 'abort')?.[1]; + expect(abortListener).toEqual(expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith('abort', abortListener); + }); + test('normalized method', async () => { let capturedRequest: RequestInit | undefined; const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => {