Skip to content

fix(codex): refresh + retry once on a 401 (biggest reliability gap)#108

Merged
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
fix/codex-401-retry
Jul 6, 2026
Merged

fix(codex): refresh + retry once on a 401 (biggest reliability gap)#108
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
fix/codex-401-retry

Conversation

@aayambansal

Copy link
Copy Markdown
Member

The single most important fix for making Codex (ChatGPT-subscription) sign-in reliable.

Problem

The loader's token refresh is time-based only — it refreshes shortly before the local expires. But a Codex access token can be revoked/invalidated server-side before that (a password change, an admin revoke, clock skew, or a lost cross-process refresh race between the CLI and the workspace server). That surfaced as a 401 returned verbatim: the request failed and the only recovery was a manual openscience keys signin. The response branch only handled 429/403 quota — never 401.

Fix

On a 401 for a Codex route, force one refresh and retry once:

  • Re-read the latest persisted auth first (a sibling process may have already rotated the pair) and adopt a newer, still-valid token if present.
  • Otherwise spend the refresh token via the existing single-flight refresh and persist the rotated pair.
  • A dead refresh token surfaces the Reconnect it with keys signin message; a transient failure returns the original 401 unchanged rather than making it worse.
  • Retries at most once — it can never loop.

The send/refresh/retry control flow is extracted into a pure, dependency-injected sendWithCodex401Retry() helper.

Tests

test/plugin/codex-401-retry.test.ts: 2xx passes through without refresh; a 401 refreshes once and retries with the new token; refresh-gives-up returns the original 401; a persistent 401 stops after one retry (no loop); a fatal refresh error propagates. Existing codex-refresh / codex-device-poll tests still pass.

…gap)

The loader's proactive refresh only catches token EXPIRY. A Codex access
token can be revoked/invalidated server-side BEFORE its local `expires`
(password change, admin revoke, clock skew, or a lost cross-process
refresh race). That surfaced as a 401 returned verbatim — the request
just failed, and the only recovery was a manual `keys signin`.

Now a 401 on a Codex route forces one refresh and retries once. The
refresh re-reads the latest persisted auth first (a sibling process may
have already rotated the pair) and adopts a newer token if present,
otherwise spends the refresh token and persists the rotated pair. A dead
refresh token surfaces the reconnect message; a transient failure keeps
the original 401 rather than making things worse. Retries at most once,
so it can never loop.

Extracted sendWithCodex401Retry() as a pure, dependency-injected helper
so the send/refresh/retry control flow is unit-tested.
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openscience Ready Ready Preview, Comment Jul 6, 2026 3:35pm

Request Review

@aayambansal Aayam Bansal (aayambansal) merged commit 62ec54d into main Jul 6, 2026
12 checks passed
@aayambansal Aayam Bansal (aayambansal) deleted the fix/codex-401-retry branch July 6, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant