Don't retry "insufficient_quota" errors (#6340)

This PR makes an "insufficient quota" error fatal so we don't attempt to
retry it multiple times in the agent loop.

We have multiple bug reports from users about intermittent retry
behaviors, and this could explain some of them. With this change, we'll
eliminate the retries and surface a clear error message.

The PR is a nearly identical copy of [this
PR](https://github.com/openai/codex/pull/4837) contributed by
@abimaelmartell. The original PR has gone stale. Rather than wait for
the contributor to resolve merge conflicts, I wanted to get this change
in.
This commit is contained in:
Eric Traut
2025-11-06 17:12:01 -06:00
committed by GitHub
parent e30f65118d
commit 0c647bc566
5 changed files with 123 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ mod model_overrides;
mod model_tools;
mod otel;
mod prompt_caching;
mod quota_exceeded;
mod read_file;
mod resume;
mod review;

View File

@@ -0,0 +1,75 @@
use std::time::Duration;
use anyhow::Result;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event_with_timeout;
use pretty_assertions::assert_eq;
use serde_json::json;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn quota_exceeded_emits_single_error_event() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mut builder = test_codex();
mount_sse_once(
&server,
sse(vec![
ev_response_created("resp-1"),
json!({
"type": "response.failed",
"response": {
"id": "resp-1",
"error": {
"code": "insufficient_quota",
"message": "You exceeded your current quota, please check your plan and billing details."
}
}
}),
]),
)
.await;
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "quota?".into(),
}],
})
.await
.unwrap();
let mut error_events = 0;
loop {
let event =
wait_for_event_with_timeout(&test.codex, |_| true, Duration::from_secs(5)).await;
match event {
EventMsg::Error(err) => {
error_events += 1;
assert_eq!(
err.message,
"Quota exceeded. Check your plan and billing details."
);
}
EventMsg::TaskComplete(_) => break,
_ => {}
}
}
assert_eq!(error_events, 1, "expected exactly one Codex:Error event");
Ok(())
}