Skip to content

fix(codex): recover from refresh-token rotation races instead of dying#20

Merged
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
codex-refresh-rotation
Jul 4, 2026
Merged

fix(codex): recover from refresh-token rotation races instead of dying#20
Aayam Bansal (aayambansal) merged 1 commit into
mainfrom
codex-refresh-rotation

Conversation

@aayambansal

Copy link
Copy Markdown
Member

Users whose Codex (Sign in with ChatGPT) usage limit gets exhausted end up with a permanently broken openai-codex provider — even after the ChatGPT limit resets. Root cause: ChatGPT rotates the refresh token on every refresh, and our single-flight guard only covers one process. While requests retry against an exhausted limit, the CLI and the workspace server race the refresh; the loser keeps a revoked refresh token, and from then on every refresh throws Token refresh failed: 400 until the user figures out they must disconnect and re-login.

The loader's refresh path now:

  • on refresh failure, re-reads persisted auth (the racing winner already stored the rotated token pair)
  • adopts a still-valid access token, or retries the refresh once with the rotated refresh token
  • only then fails, with an actionable message ('Reconnect it with openscience auth login → Codex — Sign in with ChatGPT') instead of the raw 400

Verified: turbo typecheck clean; full backend suite 828/828; codex + billing-gate tests 20/20. The loader fetch closure has no unit harness (existing tests cover only the exported JWT helpers) — noted for future work.

ChatGPT rotates the refresh token on every refresh, and the single-flight
guard only covers one process. When two openscience processes (CLI +
workspace server) race a refresh — common while requests retry against an
exhausted usage limit — the loser persists nothing and keeps a revoked
token, so every later refresh fails even after the usage limit resets.
The only way out was noticing yourself that you had to re-login.

The loader now treats a failed refresh as possibly-stale local state:
re-read persisted auth (the racing winner already stored the rotated
pair), adopt a still-valid access token or retry the refresh with the
rotated refresh token, and only then fail — with an actionable
'reconnect with openscience auth login' message instead of a raw
'Token refresh failed: 400'.
@vercel

vercel Bot commented Jul 4, 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 4, 2026 8:32pm

Request Review

@aayambansal Aayam Bansal (aayambansal) merged commit fa6a063 into main Jul 4, 2026
9 checks passed
@aayambansal Aayam Bansal (aayambansal) deleted the codex-refresh-rotation branch July 4, 2026 20:34
Aayam Bansal (aayambansal) added a commit that referenced this pull request Jul 7, 2026
…ot (#128)

- clearSession() removes the synced credential artifacts (synced-env.json,
  openscience-synced.json) and unsets the env BEFORE deleting the session
  file. A crash after unlinking the session but before removing synced-env.json
  otherwise left preload-env.ts replaying the managed thk_ key on the next boot,
  so the signed-out account's wallet kept being debited. (#37)
- Seed syncedSecretValues from the on-disk snapshot at import. preload-env.ts
  replays synced-env.json into process.env but never seeded this set, so on a
  fresh process where no in-process sync runs (the common steady state) the set
  was empty and a disk-replayed synced secret that isn't a thk_ value was
  neither stripped from subprocess env nor masked by redactSecrets(). (#20)
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