Skip to content

[Bug] Codex pool plan stays "free" across token refresh and ocx restart until a manual quota refresh (never re-derived from JWT chatgpt_plan_type) #1989

Closed

Description

@tmdgusya

Client or integration

OpenCodex CLI (ocx account list PLAN/LABEL column; same plan field on the management API DTO)

Area

Authentication and account pool

Summary

On 2.24.2, ocx account list renders PLAN/LABEL free for a ChatGPT pro account, and the persisted codexAccounts[].plan stays free across access-token refreshes and ocx restart. The plan is only corrected by a manual quota refresh (ocx account refresh openai → WHAM fetch). Between WHAM refreshes nothing re-derives the plan, even though the current access token itself carries the authoritative claim:

JWT claim https://api.openai.com/auth.chatgpt_plan_type = "pro"   (both main and pool account)
ocx account list PLAN/LABEL                                     = "free"  (both rows)

This is a sibling of #724 (fixed by #750) but survives it: #750 made the refresh path persist the fresh WHAM plan_type, while the non-refresh paths (startup, ocx restart, token refresh, credential injection) still trust the stored plan unboundedly.

Observed sequence (sanitized, no re-login at any point):

  1. ocx account list → main: free, chatgpt-…: free; both access tokens decode to chatgpt_plan_type: "pro".
  2. ocx restart → unchanged (free).
  3. ocx account refresh openai → both rows flip to pro; ~/.opencodex/config.json codexAccounts[].plan becomes "pro".

Functional impact beyond the badge: codexQuotaWindowForPlan() (src/codex/quota.ts) treats free as a 30-day/monthly-window plan and pro as weekly, and pool routing/auto-switch scores on the stored plan (src/codex/routing.ts). A pro account misread as free is therefore scored on the wrong window shape until a manual refresh happens.

Reproduction

  1. Store a Codex pool account while its WHAM plan_type is free (e.g. added before the subscription was visible upstream, or after a plan change upstream).
  2. Let the access token refresh and run ocx restart (no WHAM quota refresh in between).
  3. ocx account list still shows PLAN/LABEL free although the token decodes to pro.
  4. ocx account refresh openai → label becomes pro.

Expected: a locally decodable chatgpt_plan_type claim that contradicts the persisted plan should either correct it (generation-safe persist, same as #750) or mark it unknown for display/scoring; a stale free must not outrank token evidence with no bound on its age.

Suggested fix (matching 2.24.2 layout)

  • src/codex/main-account.ts already imports decodeJwtPayload; derive a candidate plan from the access-token claim and include it in the fallback chain used for the main account in src/codex/auth-api.ts (nonEmptyPlan(data.plan_type) ?? nonEmptyPlan(cached?.plan) ?? nonEmptyPlan(getMainAccountPlan())) and validate persisted codexAccounts[].plan whenever credentials refresh (same generation-safe persist path as fix(codex): persist refreshed pool account plans #750).
  • Display nit in src/cli/account-api.ts: label: a.alias ?? a.plan ?? a.email — a stale plan currently outranks the (correct) stored email; prefer the email or only render the plan when it is known-fresh.

Version

opencodex 2.24.2 (@bitkyc08/opencodex, npm global, bundled runtime)

Operating system

macOS (Apple Silicon, Darwin 25.4.0); codex-cli 0.147.0 shimmed through the proxy

Provider and model

OpenAI Codex account pool; model-independent

Logs or error output

$ ocx account list
PROVIDER  TYPE   ID                     PLAN/LABEL  PRIORITY  STATUS
openai    codex  main                   free        -1
openai    codex  chatgpt-…              free        0         selected
kimi      oauth  9cc6fc4a…              Account 1   -         active

# both codex access tokens: chatgpt_plan_type=pro, same chatgpt_account_id (same physical account)

$ ocx restart && ocx account list   # unchanged, still free

$ ocx account refresh openai
main          d***h@gmail.com pro  weekly 0% …
chatgpt-…     d***h@gmail.com pro  weekly 0% … monthly 1% …

$ ocx account list
openai    codex  main                   pro         -1
openai    codex  chatgpt-…              pro         0         selected

Screenshots and supporting files

Relevant 2.24.2 paths: src/codex/auth-api.ts (plan fallback chain + DTO), src/codex/main-account.ts (WHAM-only plan, decodeJwtPayload already imported), src/codex/quota.ts (codexQuotaWindowForPlan), src/codex/routing.ts (stored-plan scoring), src/cli/account-api.ts (label precedence).

Redacted configuration

{
  "providers": { "openai": { "codexAccountMode": "pool" } },
  "codexAccounts": [
    { "isMain": false, "id": "chatgpt-…", "plan": "free → pro (only after manual refresh)" }
  ]
}

Checks

Activity

  1. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    cliCLI, config inject, packaging flags
    on Aug 18, 2026
  2. added 2 commits that reference this issue on Aug 18, 2026
    02c7e08
    8afca6e
  3. lidge-jun commented on Aug 18, 2026

    @lidge-jun
    Owner

    Fixed on dev: #1998 merged. The pool plan is now re-derived from the live JWT chatgpt_plan_type claim between WHAM refreshes (new src/codex/plan-from-token.ts, generation-safe persistence), with a live WHAM plan_type still taking precedence — so the plan no longer sticks at "free" across token refresh and ocx restart. Validated: codex-plan + codex-auth-api suites 189/0 on the merged tree. Reaches latest with the next release train.

  4. added 2 commits that reference this issue on Sep 17, 2026
    6734910
    afe5fad
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