Skip to content

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

Closed

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:87 — Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 — missing expires_in → undefined * 1000 = NaN
  2. src/oauth/kimi.ts:163 — Date.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:55 — Date.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:498 — Date.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.

Activity

  1. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    on Aug 10, 2026
  2. Bruce-Yii commented on Aug 10, 2026

    @Bruce-Yii
    ContributorAuthor

    Opened a fix: #1418 (base dev, maintainer edits enabled).

    All four sites now use the typeof === "number" && Number.isFinite(...) pattern already established at src/oauth/xai.ts:128-129, falling back to a 3600s default (or rejecting, for Kimi's required-field semantics). No getLoginStatus / expiry-comparison changes — parse/adoption time only, per the lazy-refresh contract from #1369.

    Tests: 5 new (account-store 1, kimi 1, anthropic 1, chatgpt 2); targeted 47/47, OAuth+anthropic+account-store suite 406/406, tsc --noEmit clean.

  3. Ingwannu commented on Aug 10, 2026

    @Ingwannu
    Owner

    Verified against PR #1418 exact head 355b69e5b003edebe3f543d1818a9618fa075b14. The negative-duration and computed-overflow gaps are fixed across all four adoption paths, the fallback windows are pinned by tests, and the failed macOS job passed on rerun. I approved the authentication-boundary change after the exact-head security and CI review.

    The PR is still draft because the author has not yet ticked the now-proven final checklist item, and an additional maintainer review is still requested. Leaving this issue open until the fix is actually integrated; PR-to-dev alone would not auto-close an issue whose default branch is main.

  4. Ingwannu commented on Aug 10, 2026

    @Ingwannu
    Owner

    Status correction: PR #1418 is now ready for review at the same approved exact head 355b69e5b003edebe3f543d1818a9618fa075b14. The author completed the readiness checklist, the PR carries maintainer-sponsored and review-ready, and the authentication-boundary approval remains current.

    lidge-jun and Wibias are still requested for independent review. Keeping this issue open until the PR is actually integrated; no direct merge is being performed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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