Skip to content

[Bug]: imported local CLI credential with invalid expires_at is adopted and never refreshed; ocx status shows "logged in" #1366

Closed

Description

@Bruce-Yii

Client or integration

Codex CLI

Area

Authentication and account pool

Summary

An imported local CLI credential (Grok ~/.grok/auth.json or Claude .credentials.json) whose expires_at is invalid (e.g. "garbage", "", "1970-01-01") is treated as valid and never refreshed:

  1. src/oauth/local-token-detect.ts:27 — new Date(entry.expires_at as string).getTime() returns NaN for any unparseable string.
  2. shouldAdoptGrokGeneration() (src/oauth/local-token-detect.ts:52-62) then behaves incorrectly for NaN:
    • disk.expires <= now + refreshSkewMs is always false for NaN → the expired/garbage credential passes the expiry gate;
    • bothExpiriesExist = stored.expires > 0 && disk.expires > 0 is false (NaN > 0 is false) → the function falls through to return true, so the disk credential is adopted as the authoritative generation.
  3. authoritative() (src/oauth/index.ts:441) therefore replaces the stored credential with the garbage one, and the refresh path never re-validates it — the stale credential occupies the account slot indefinitely.
  4. getLoginStatus() (src/oauth/index.ts:1178) reports loggedIn: !!cred (credential exists), ignoring expires/needsReauth; ocx status then prints ✓ logged in in the "OAuth logins" section for an expired or unparseable credential (the separate health block may flag needsReauth, but the logins section is the misleading first screen).

Same structural issue as a stale/epoch-garbage expires_at (e.g. 1970-01-22) observed in a real CLI credential file: the import is adopted even though it can never be refreshed.

Reproduction

  1. Create a fake Grok CLI credential:
    {
      "https://auth.x.ai::1": {
        "key": "xai-stub-access",
        "refresh_token": "xai-stub-refresh",
        "expires_at": "not-a-date"
      }
    }
    at ~/.grok/auth.json (or a Claude .credentials.json with expiresAt: "garbage").
  2. Run ocx login xai --import-local (or any flow that reaches detectGrokCliToken/authoritative).
  3. Observe: the invalid-expiry credential is adopted (shouldAdoptGrokGeneration returns true); ocx status shows xai ✓ logged in; no refresh is attempted because the expiry comparison is NaN.

Expected: an unparseable/expired expires_at must be treated as unknown → refresh required (or reject the import), never as "valid and never refresh".

Version

2.11.1 (121f1ad9)

Operating system

Cross-platform (Linux/macOS CLI credential paths; Windows unaffected by ~/.grok but the same parsing logic applies)

Provider and model

xai (grok) / anthropic (claude) — local CLI token import

Checks

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

Activity

  1. github-actions commented on Aug 9, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  2. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    cliCLI, config inject, packaging flags
    on Aug 9, 2026
  3. Bruce-Yii commented on Aug 9, 2026

    @Bruce-Yii
    ContributorAuthor

    Opened a fix: #1369 (base dev).

    Summary of the approach:

    • Number.isFinite guards at both parse sites (detectGrokCliToken / parseClaudeOauthPayload) → invalid/NaN expiry becomes 0 ("unknown", forces refresh validation) instead of flowing downstream as NaN.
    • shouldAdoptGrokGeneration explicitly refuses non-finite disk expiries.
    • getLoginStatus.loggedIn now also requires the active account to be not needsReauth and not expired — which automatically fixes the ocx status "OAuth logins" display via oauthLoginSummary().

    Tests: 12 new (local-token-detect 9, oauth-status-privacy 3); OAuth suite 172/172 pass; tsc --noEmit clean.

  4. lidge-jun commented on Aug 10, 2026

    @lidge-jun
    Owner

    Fixed by #1369 (831a120ea, merged as e8ce2b93d) and released in v2.12.0, now on npm as @bitkyc08/opencodex@2.12.0.

    The released build guards expiry parsing at both call sites, so an invalid or NaN expires_at normalizes to "unknown" and forces refresh validation instead of flowing downstream. A non-finite on-disk expiry can no longer be adopted as a valid generation, and ocx status no longer reports "logged in" for an account that needs reauthentication or has expired.

    Verified in the released tree: git merge-base --is-ancestor 831a120ea 9c051342d exits 0, and git tag --contains 831a120ea includes v2.12.0.

    Thanks for the precise reproduction, and thanks to @Bruce-Yii for the fix. Please reopen if you still see this on 2.12.0.

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 workingcliCLI, config inject, packaging flags

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions