From 80fa92a491c67ae98c43ea723487db640d99857f Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 16 May 2026 23:39:41 -0700 Subject: [PATCH] =?UTF-8?q?fix(codex):=20rotate=20pool=20on=20usage=20limi?= =?UTF-8?q?t=20429=20=E2=80=94=20port=20to=20extracted=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit e51d74ab9 by Maxim Esipov targeted _extract_api_error_context and _recover_with_credential_pool in pre-refactor run_agent.py. Both bodies now live in agent/agent_runtime_helpers.py — re-applied to that module: - extract_api_error_context: payload.get('type') added to the reason fallback chain (Codex error bodies use 'type' instead of 'code'/'error') - recover_with_credential_pool: usage_limit_reached detection in the rate_limit branch — skip the retry-once-then-rotate dance and rotate immediately when the body says the per-account usage limit hit. Co-authored-by: Maxim Esipov --- agent/agent_runtime_helpers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index ea48163ba0b..bac21f14061 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -583,7 +583,15 @@ def recover_with_credential_pool( return False, has_retried_429 if effective_reason == FailoverReason.rate_limit: - if not has_retried_429: + usage_limit_reached = False + if error_context: + context_reason = str(error_context.get("reason") or "").lower() + context_message = str(error_context.get("message") or "").lower() + usage_limit_reached = ( + "usage_limit_reached" in context_reason + or "usage limit has been reached" in context_message + ) + if not has_retried_429 and not usage_limit_reached: return False, True rotate_status = status_code if status_code is not None else 429 next_entry = pool.mark_exhausted_and_rotate(status_code=rotate_status, error_context=error_context) @@ -1910,7 +1918,7 @@ def extract_api_error_context(error: Exception) -> Dict[str, Any]: if isinstance(body, dict): payload = body.get("error") if isinstance(body.get("error"), dict) else body if isinstance(payload, dict): - reason = payload.get("code") or payload.get("error") + reason = payload.get("code") or payload.get("type") or payload.get("error") if isinstance(reason, str) and reason.strip(): context["reason"] = reason.strip() message = payload.get("message") or payload.get("error_description")