fix(auth): retry Codex device-code login on 429 with clear rate-limit message (#47860)
The OpenAI device-code login (POST auth.openai.com/.../deviceauth/usercode) had no retry or 429 handling — a transient throttle from OpenAI surfaced as a bare "Device code request returned status 429" with no guidance, reading as a hard login failure. - Retry the device-code request with capped exponential backoff (honoring Retry-After), up to 4 attempts. - On persistent 429, raise a clear AuthError tagged CODEX_RATE_LIMITED_CODE (classified transient, not a credential problem) with a wait hint. - Apply the same 429 classification to the token-exchange step (same bug class). Unrelated to PR #47399 (Responses-API cache headers); this is the OAuth device-code path in hermes_cli/auth.py.
This commit is contained in:
parent
06d907dc4e
commit
cbfa018aef
2 changed files with 150 additions and 12 deletions
|
|
@ -7231,23 +7231,61 @@ def _codex_device_code_login() -> Dict[str, Any]:
|
|||
issuer = "https://auth.openai.com"
|
||||
client_id = CODEX_OAUTH_CLIENT_ID
|
||||
|
||||
# Step 1: Request device code
|
||||
try:
|
||||
with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
|
||||
resp = client.post(
|
||||
f"{issuer}/api/accounts/deviceauth/usercode",
|
||||
json={"client_id": client_id},
|
||||
headers={"Content-Type": "application/json"},
|
||||
# Step 1: Request device code. OpenAI's auth endpoint rate-limits this
|
||||
# request (HTTP 429) when login is attempted too often from the same
|
||||
# IP/account — retry with capped backoff (honoring ``Retry-After``)
|
||||
# before surfacing a clear, actionable message instead of a bare status.
|
||||
resp = None
|
||||
max_attempts = 4
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
|
||||
resp = client.post(
|
||||
f"{issuer}/api/accounts/deviceauth/usercode",
|
||||
json={"client_id": client_id},
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
except Exception as exc:
|
||||
raise AuthError(
|
||||
f"Failed to request device code: {exc}",
|
||||
provider="openai-codex", code="device_code_request_failed",
|
||||
)
|
||||
except Exception as exc:
|
||||
|
||||
if resp.status_code != 429:
|
||||
break
|
||||
|
||||
if attempt < max_attempts:
|
||||
retry_after = _parse_retry_after_seconds(
|
||||
getattr(resp, "headers", None)
|
||||
)
|
||||
# Exponential backoff (2s, 4s, 8s) capped, preferring the
|
||||
# server-provided Retry-After when present.
|
||||
delay = retry_after if retry_after is not None else 2 ** attempt
|
||||
delay = max(1, min(int(delay), 60))
|
||||
print(
|
||||
"OpenAI is rate-limiting login requests "
|
||||
f"(429); retrying in {delay}s..."
|
||||
)
|
||||
_time.sleep(delay)
|
||||
|
||||
if resp is not None and resp.status_code == 429:
|
||||
retry_after = _parse_retry_after_seconds(getattr(resp, "headers", None))
|
||||
wait_hint = (
|
||||
f" Try again in about {retry_after}s."
|
||||
if retry_after is not None
|
||||
else " Wait a minute and run the login again."
|
||||
)
|
||||
raise AuthError(
|
||||
f"Failed to request device code: {exc}",
|
||||
provider="openai-codex", code="device_code_request_failed",
|
||||
"OpenAI is rate-limiting Codex login requests (HTTP 429). "
|
||||
"This is a temporary throttle on OpenAI's side, not a credential "
|
||||
f"problem.{wait_hint}",
|
||||
provider="openai-codex", code=CODEX_RATE_LIMITED_CODE,
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
if resp is None or resp.status_code != 200:
|
||||
status = resp.status_code if resp is not None else "unknown"
|
||||
raise AuthError(
|
||||
f"Device code request returned status {resp.status_code}.",
|
||||
f"Device code request returned status {status}.",
|
||||
provider="openai-codex", code="device_code_request_error",
|
||||
)
|
||||
|
||||
|
|
@ -7335,6 +7373,22 @@ def _codex_device_code_login() -> Dict[str, Any]:
|
|||
provider="openai-codex", code="token_exchange_failed",
|
||||
)
|
||||
|
||||
if token_resp.status_code == 429:
|
||||
retry_after = _parse_retry_after_seconds(
|
||||
getattr(token_resp, "headers", None)
|
||||
)
|
||||
wait_hint = (
|
||||
f" Try again in about {retry_after}s."
|
||||
if retry_after is not None
|
||||
else " Wait a minute and run the login again."
|
||||
)
|
||||
raise AuthError(
|
||||
"OpenAI is rate-limiting Codex login requests (HTTP 429) during "
|
||||
"token exchange. This is a temporary throttle on OpenAI's side, "
|
||||
f"not a credential problem.{wait_hint}",
|
||||
provider="openai-codex", code=CODEX_RATE_LIMITED_CODE,
|
||||
)
|
||||
|
||||
if token_resp.status_code != 200:
|
||||
raise AuthError(
|
||||
f"Token exchange returned status {token_resp.status_code}.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue