From 4f63d8ebc91a516ba721dfdba4cc8f55ebbc48c3 Mon Sep 17 00:00:00 2001 From: Oxygen <1391083091@qq.com> Date: Sat, 6 Jun 2026 01:35:58 +0800 Subject: [PATCH 1/2] fix: restore 60-second cap on Retry-After header values The Retry-After cap was accidentally removed in a chore commit. This restores it to prevent excessive wait times. Fixes #1840 Co-Authored-By: Claude Opus 4.8 --- src/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index ac082a5e1..e85897348 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1026,9 +1026,9 @@ export class OpenAI { } } - // If the API asks us to wait a certain amount of time, just do what it - // says, but otherwise calculate a default - if (timeoutMillis === undefined) { + // If the API asks us to wait a certain amount of time (capped at 60 seconds), + // just do what it says, but otherwise calculate a default + if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } From d0eaae6f265cf3ca721a7dadb155a0bd2b5cdd21 Mon Sep 17 00:00:00 2001 From: Oxygen <100782273+Oxygen56@users.noreply.github.com> Date: Thu, 9 Jul 2026 01:33:12 +0800 Subject: [PATCH 2/2] fix: honor Retry-After at 60 second cap --- src/client.ts | 2 +- tests/index.test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index e85897348..c1787c2ff 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1028,7 +1028,7 @@ export class OpenAI { // If the API asks us to wait a certain amount of time (capped at 60 seconds), // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis <= 60 * 1000)) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } diff --git a/tests/index.test.ts b/tests/index.test.ts index 028eccb17..fde6ee532 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -885,6 +885,47 @@ describe('retries', () => { expect(count).toEqual(3); }); + test.each([ + ['Retry-After', '60'], + ['Retry-After-Ms', '60000'], + ])('retry on 429 honors %s at the 60-second cap', async (headerName, headerValue) => { + jest.useFakeTimers(); + try { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Response(undefined, { + status: 429, + headers: { + [headerName]: headerValue, + }, + }); + } + return new Response(JSON.stringify({ a: 1 }), { + headers: { 'Content-Type': 'application/json' }, + }); + }; + + const client = new OpenAI({ + apiKey: 'My API Key', + adminAPIKey: 'My Admin API Key', + fetch: testFetch, + }); + + const request = client.request({ path: '/foo', method: 'get' }); + await Promise.resolve(); + await jest.advanceTimersByTimeAsync(60 * 1000); + + await expect(request).resolves.toEqual({ a: 1 }); + expect(count).toEqual(2); + } finally { + jest.useRealTimers(); + } + }); + describe('auth', () => { test('apiKey', async () => { const client = new OpenAI({