Skip to content

[Bug]: OAuth token responses with invalid expires_in produce NaN expiries that never refresh #1417

Description

@Bruce-Yii

Client or integration

Codex CLI / Direct HTTP/API client

Area

Authentication and account pool

Summary

OAuth token responses are parsed into credential expiries in four places without a Number.isFinite guard on expires_in. A malformed upstream response (missing, NaN, or string expires_in) yields a NaN expiry that flows into downstream time comparisons, where NaN <= x is always false — the credential is treated as never-expiring and never refreshed. This is the same data-quality class as the recently merged #1366/#1369 fix (Number.isFinite guards on local token expiry parsing) that remains unfixed at the token-response parse sites.

Affected sites:

  1. src/oauth/anthropic.ts:87Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 — missing expires_inundefined * 1000 = NaN
  2. src/oauth/kimi.ts:163Date.now() + payload.expires_in * 1000 - OAUTH_EXPIRY_SKEW_MS — line 154 checks typeof !== "number", but NaN passes (typeof NaN === "number")
  3. src/oauth/chatgpt.ts:55Date.now() + ((data.expires_in as number) ?? 3600) * 1000?? only guards null/undefined; NaN or a numeric string passes through
  4. src/codex/account-store.ts:498Date.now() + data.expires_in * 1000 — no validation at all

Reference implementation with the correct guard already exists at src/oauth/xai.ts:128-129:

const expiresIn = typeof payload.expires_in === "number" && Number.isFinite(payload.expires_in) ? payload.expires_in : 3600;

Reproduction

Mock an upstream OAuth token response with expires_in missing (or null/NaN):

{ "access_token": "...", "refresh_token": "...", "expires_in": null }

Observe the stored credential carries expires: NaN, so expires <= now is always false — the credential never refreshes and ocx status reports it as logged in indefinitely (its health/refresh machinery cannot detect expiry).

Expected: a non-finite expires_in falls back to the same default used for a missing field (e.g. 3600s) so the credential stays refreshable.

Version

dev 0de4fd2d

Operating system

Cross-platform

Provider and model

anthropic / kimi / chatgpt / codex (account-store) — OAuth token response parsing

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions