Skip to content

[rmcp-client] Refresh OAuth tokens before initialize - #26237

Closed
caseychow-oai wants to merge 5 commits into
mainfrom
caseychow/codex/fix-rmcp-oauth-refresh
Closed

[rmcp-client] Refresh OAuth tokens before initialize#26237
caseychow-oai wants to merge 5 commits into
mainfrom
caseychow/codex/fix-rmcp-oauth-refresh

Conversation

@caseychow-oai

@caseychow-oai caseychow-oai commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Context

Runlayer exposed a Desktop-only failure where persisted OAuth credentials contained an expired access token and a still-valid refresh token. New Codex Desktop sessions loaded those credentials and attempted the first Streamable HTTP MCP initialize with the expired access token, so the server returned 401 invalid_token until the user fully reauthenticated.

The owning failure was in rmcp-client: OAuth-backed Streamable HTTP clients reconstructed persisted credentials but did not refresh them before the initial initialize handshake. The follow-up hardening in this PR also closes races around refresh-token rotation, logout, concurrent processes, and the shared fallback credential store.

Changes

  • Refresh expired persisted OAuth credentials before the first OAuth-backed Streamable HTTP initialize handshake.
  • Compare stable credential fields independently from reconstructed expires_in metadata while retaining the absolute persisted expiry.
  • Serialize same-server refreshes across tasks and processes, while keeping unexpired credential reads independent from writable lock storage.
  • Reload the persisted credential generation while holding the refresh transaction lock so logout, reauthentication, or another process refresh cannot be overwritten by stale in-memory state.
  • Atomically update the shared fallback credential file and serialize cross-process read-modify-write mutations for different MCP servers.
  • Preserve an existing refresh token when a provider omits rotation, and atomically persist rotated refresh tokens when provided.
  • Keep refresh and RMCP initialize under one caller timeout budget. A timed-out refresh can finish and retry detached persistence without creating an independent lock timeout.
  • Classify only permanent refresh failures such as invalid_grant or a missing refresh token as auth-required; transient provider and transport failures remain initialization errors.
  • Route OAuth login and logout through the async credential transaction path.

Key Decisions

  • The behavior remains owned by rmcp-client at the pending transport connection point, so only OAuth-backed Streamable HTTP transports are affected.
  • Reactive recovery after a server invalid_token response remains defense-in-depth and separate from this primary startup fix.
  • The persisted credential value is treated as a generation snapshot. A changed or removed value wins over a stale in-memory manager before refresh or post-call persistence proceeds.
  • Persistence after a successful refresh remains best-effort for the active session, but detached refreshes retain retry ownership so rotated credentials are not silently abandoned after a transient storage failure.
  • Tests use file-backed credentials and subprocesses to exercise the same load, refresh, rotation, logout, timeout, and cross-process persistence behavior without depending on macOS Keychain.

Validation

  • cargo test -p codex-rmcp-client --test streamable_http_recovery (17 passed)
  • cargo test -p codex-rmcp-client oauth::tests --lib (12 passed)
  • cargo +1.95.0 check -p codex-cli
  • just bazel-lock-update
  • just bazel-lock-check
  • just fmt
  • just fix -p codex-rmcp-client
  • git diff --check

The focused tests passed before the final required just fmt and just fix; per repository instructions they were not rerun afterward.

Codex Thread

@caseychow-oai
caseychow-oai force-pushed the caseychow/codex/fix-rmcp-oauth-refresh branch from 9afcaed to afdf109 Compare June 3, 2026 21:10
@anp-oai

anp-oai commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@caseychow-oai

Copy link
Copy Markdown
Contributor Author

superceded by #26482

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.

2 participants