Skip to content

Commit 47c6834

Browse files
authored
(feat) expontential back-off when encountering rate limit errors (openai#153)
...and try to parse the suggested time from the error message while we don't yet have this in a structured way --------- Signed-off-by: Thibault Sottiaux <tibo@openai.com>
1 parent b8aea23 commit 47c6834

2 files changed

Lines changed: 51 additions & 25 deletions

File tree

codex-cli/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-cli/src/utils/agent/agent-loop.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import OpenAI, { APIConnectionTimeoutError } from "openai";
2424

2525
// Wait time before retrying after rate limit errors (ms).
2626
const RATE_LIMIT_RETRY_WAIT_MS = parseInt(
27-
process.env["OPENAI_RATE_LIMIT_RETRY_WAIT_MS"] || "15000",
27+
process.env["OPENAI_RATE_LIMIT_RETRY_WAIT_MS"] || "2500",
2828
10,
2929
);
3030

@@ -569,11 +569,6 @@ export class AgentLoop {
569569
);
570570
continue;
571571
}
572-
const isRateLimit =
573-
status === 429 ||
574-
errCtx.code === "rate_limit_exceeded" ||
575-
errCtx.type === "rate_limit_exceeded" ||
576-
/rate limit/i.test(errCtx.message ?? "");
577572

578573
const isTooManyTokensError =
579574
(errCtx.param === "max_tokens" ||
@@ -597,30 +592,61 @@ export class AgentLoop {
597592
return;
598593
}
599594

595+
const isRateLimit =
596+
status === 429 ||
597+
errCtx.code === "rate_limit_exceeded" ||
598+
errCtx.type === "rate_limit_exceeded" ||
599+
/rate limit/i.test(errCtx.message ?? "");
600600
if (isRateLimit) {
601601
if (attempt < MAX_RETRIES) {
602+
// Exponential backoff: base wait * 2^(attempt-1), or use suggested retry time
603+
// if provided.
604+
let delayMs = RATE_LIMIT_RETRY_WAIT_MS * 2 ** (attempt - 1);
605+
606+
// Parse suggested retry time from error message, e.g., "Please try again in 1.3s"
607+
const msg = errCtx?.message ?? "";
608+
const m = /retry again in ([\d.]+)s/i.exec(msg);
609+
if (m && m[1]) {
610+
const suggested = parseFloat(m[1]) * 1000;
611+
if (!Number.isNaN(suggested)) {
612+
delayMs = suggested;
613+
}
614+
}
602615
log(
603-
`OpenAI rate limit exceeded (attempt ${attempt}/${MAX_RETRIES}), retrying in ${RATE_LIMIT_RETRY_WAIT_MS} ms...`,
616+
`OpenAI rate limit exceeded (attempt ${attempt}/${MAX_RETRIES}), retrying in ${Math.round(
617+
delayMs,
618+
)} ms...`,
604619
);
605620
// eslint-disable-next-line no-await-in-loop
606-
await new Promise((resolve) =>
607-
setTimeout(resolve, RATE_LIMIT_RETRY_WAIT_MS),
608-
);
621+
await new Promise((resolve) => setTimeout(resolve, delayMs));
609622
continue;
623+
} else {
624+
// We have exhausted all retry attempts. Surface a message so the user understands
625+
// why the request failed and can decide how to proceed (e.g. wait and retry later
626+
// or switch to a different model / account).
627+
628+
const errorDetails = [
629+
`Status: ${status || "unknown"}`,
630+
`Code: ${errCtx.code || "unknown"}`,
631+
`Type: ${errCtx.type || "unknown"}`,
632+
`Message: ${errCtx.message || "unknown"}`,
633+
].join(", ");
634+
635+
this.onItem({
636+
id: `error-${Date.now()}`,
637+
type: "message",
638+
role: "system",
639+
content: [
640+
{
641+
type: "input_text",
642+
text: `⚠️ Rate limit reached. Error details: ${errorDetails}. Please try again later.`,
643+
},
644+
],
645+
});
646+
647+
this.onLoading(false);
648+
return;
610649
}
611-
this.onItem({
612-
id: `error-${Date.now()}`,
613-
type: "message",
614-
role: "system",
615-
content: [
616-
{
617-
type: "input_text",
618-
text: "⚠️ Rate limit reached while contacting OpenAI. Please try again later.",
619-
},
620-
],
621-
});
622-
this.onLoading(false);
623-
return;
624650
}
625651

626652
const isClientError =

0 commit comments

Comments
 (0)