Skip to content

Commit 1e55e94

Browse files
committed
fix(provider): keep a positive Codex retry floor
A zero or negative Codex reset window is absent, so the retry uses the header or default floor instead of returning 0 and skipping it.
1 parent 25998b8 commit 1e55e94

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

‎src/inference-gateway-error.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,43 @@ describe("normalizeInferenceErrorForRetry", () => {
325325
expect(normalized.retryAfterMs).toBe(7_000);
326326
});
327327

328+
test.each([{ resets_in_seconds: 0 }, { resets_in_seconds: -5 }])(
329+
"Codex 429 with a zero/negative body reset window keeps the header retry-after (CL-10758)",
330+
(window) => {
331+
const normalized = normalizeInferenceErrorForRetry({
332+
...BARE_429,
333+
providerId: "codex/acme-labs",
334+
raw: {
335+
error: {
336+
code: "rate_limit_exceeded",
337+
message: "Too Many Requests",
338+
...window,
339+
},
340+
},
341+
});
342+
expect(normalized.category).toBe("retryable");
343+
expect(normalized.retryAfterMs).toBe(45_000);
344+
},
345+
);
346+
347+
test("Codex 429 with a zero retry_after body key keeps the header retry-after (CL-10758)", () => {
348+
// retry_after is not a Codex body signal (only resets_in_seconds et al.);
349+
// a zero there must never clobber the header floor either.
350+
const normalized = normalizeInferenceErrorForRetry({
351+
...BARE_429,
352+
providerId: "codex/acme-labs",
353+
raw: {
354+
error: {
355+
code: "rate_limit_exceeded",
356+
message: "Too Many Requests",
357+
retry_after: 0,
358+
},
359+
},
360+
});
361+
expect(normalized.category).toBe("retryable");
362+
expect(normalized.retryAfterMs).toBe(45_000);
363+
});
364+
328365
test("non-Codex 429s keep the header retry-after; a body reset is not read (CL-10664)", () => {
329366
const normalized = normalizeInferenceErrorForRetry({
330367
...BARE_429,

‎src/inference-gateway-error.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,16 @@ export function isCodexShortRateLimitInferenceError(
434434
/**
435435
* Codex sends its reset window in the 429 body, not a header; when a bare
436436
* rate-limit body carries one, retry on that window instead of the policy
437-
* default. Exact usage-limit codes never reach here (the skeleton vetoes).
437+
* default. A zero/negative window is not a real ETA — returning 0 would win
438+
* over the header via `??` in the skeleton and retry immediately — so it
439+
* falls through to the header/default floor. Exact usage-limit codes never
440+
* reach here (the skeleton vetoes).
438441
*/
439442
function codexRetryFromBody(error: InferenceErrorLike): number | undefined {
440443
const node = readErrorNode(coerceBody(error.raw));
441444
if (node === undefined) return undefined;
442445
const seconds = resetSecondsFromNode(node);
443-
if (seconds === undefined) return undefined;
444-
if (seconds <= 0) return 0;
446+
if (seconds === undefined || seconds <= 0) return undefined;
445447
return seconds * 1000;
446448
}
447449

0 commit comments

Comments
 (0)