MCP OAuth reauth succeeds but active session still uses stale refresh token (invalid_grant)
Open 💬 10 comments Opened Mar 9, 2026 by vinzenz
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
Summary
After re-authenticating an OAuth-enabled MCP server, the currently running Codex agent/session can continue using a stale refresh token and still fail MCP initialization with invalid_grant until the app/session is restarted.
Expected behavior
After successful codex mcp login <server>, MCP handshakes in the active session should use fresh credentials immediately (or the runtime should force re-init of auth state).
Observed behavior
codex mcp login <server>reports success.- MCP tool calls in the same running session continue failing with:
OAuth token refresh failed: invalid_grant: Invalid or expired refresh token
- Restarting the app/new session clears the issue.
Steps to reproduce
- Configure any OAuth-enabled MCP server.
- Let its refresh token become invalid/expired.
- In a running Codex session, trigger MCP usage and observe
invalid_grantfailure. - Run
codex mcp login <server>and complete OAuth successfully. - Retry MCP usage in the same running session.
- Observe continued
invalid_grantuntil restart/new session.
Environment
- Codex CLI:
codex-cli 0.107.0 - Platform: macOS 26.3.1 (25D2128)
- Date observed: 2026-03-10
Notes
This appears to be runtime credential cache invalidation rather than OAuth login failure. A post-login cache refresh or MCP client reinitialization would likely resolve it.
10 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Adding another data point for a private Sourcegraph Cloud MCP server.
Environment:
codex-cli 0.128.0streamable_http, OAuthhttps://<redacted>.sourcegraphcloud.com/.api/mcpObserved pattern:
list_repos,keyword_search).Captured failure from the same MCP config:
I also checked the current
rust-v0.128.0source. My read is:OAuthPersistor::refresh_if_needed()exists and callsAuthorizationManager.refresh_token()before MCP operations: https://github.com/openai/codex/blob/rust-v0.128.0/codex-rs/rmcp-client/src/oauth.rs#L346-L368refresh_oauth_if_needed(); the operation can continue with stale auth state: https://github.com/openai/codex/blob/rust-v0.128.0/codex-rs/rmcp-client/src/rmcp_client.rs#L681-L687This looks consistent with this issue and #17265: preemptive refresh exists, but
invalid_grant/ 401 does not force MCP client reinit, clear stale auth state, or prompt re-login for the active session.Adding another current data point, now with Codex CLI
0.133.0.I’m seeing the same active-session stale refresh-token behavior against an OAuth/DCR MCP server. Exiting and restarting Codex CLI restores access, suggesting that startup performs a full reconnection, but the live session does not recover after token expiry/refresh failure.
I may be oversimplifying, but from the outside it seems like an MCP auth failure during an active session should trigger a reconnect/reinitialize path for that specific MCP server, similar to what happens when the CLI is restarted. FWIW, the constant failures are burning tokens, and this is also a pain in the ass hahaha.
Server-side trace, same MCP client ID [bracketed notes added]:
I may be oversimplifying, but from the outside it seems like an MCP auth failure during an active session should trigger a reconnect/reinitialize path for that specific MCP server, similar to what happens when the CLI is restarted.Are you oversimplifying or is this just how every OAuth client has worked for the entire history of OAuth? 😅
I'm genuinely baffled that this bug has been present for so long... no one at OpenAI uses an OAuth-backed MCP with a long-running client session? Like.. really?
I'm having similar issues, FWIW.
Hi @vinzenz, the stale refresh token persisting after re-authentication is a subtle state management issue — the session holds a reference to the old token pair and doesn't invalidate it when the new pair is created.
The fix is conceptually straightforward but requires careful implementation: each OAuth session should have a unique session ID, and when re-authentication completes with a new token pair, the old session ID should be invalidated so any in-flight requests using the old refresh token fail fast instead of hanging.
The fact that it requires an app restart to clear the stale state suggests the session ID is tied to the process lifetime rather than the auth flow. Have you confirmed whether this is a client-side or server-side issue?
I am running into this issue constantly, I can't imagine other's aren't as MCP OAuth is a VERY common use case. How is this not a priority issue?
Adding a Windows Codex Desktop data point that appears to combine the stale-auth recovery issue here with the tool-import symptom in #20009.
Environment
0.137.0-alpha.4streamable_http, OAuthhttps://mcp.app.woodstock.co/mcp)Observed sequence
expires_in: 86399(~24 hours) and does not issue a refresh token.<server>|<id>.Codex MCP Credentials).initializePOST to the configured server returnedHTTP 200,Content-Type: text/event-stream, and a valid MCP initialize result.Repeated Codex log state
There is never a corresponding
listed MCP server tools ... server_name=woodstockevent. Built-incodex_appsdoes return its tool list normally.Additional auth behavior
HTTP 401, but in this case the response did not include aWWW-Authenticateheader.access_token,token_type,expires_in, andscope, but norefresh_token.Expected behavior
When a remote MCP access token expires and no refresh token exists, Codex should:
The direct authenticated
initializesucceeding while Codex remains stuck strongly suggests stale runtime auth/connection state rather than a currently invalid token or unavailable MCP server.Additional reproduction from Codex Desktop on macOS (app-server 0.144.2), 2026-07-13. The MCP endpoint is a private OAuth-backed Streamable HTTP server; no static bearer-token header is configured.
Evidence:
expires_in=900).codex execprocess updated the same MCP credential record without runningcodex mcp loginor a browser flow. The new stored access token hadexpires_in=28800and expiry 02:04:50 MSK the next day. This confirms that automatic refresh and persistence can succeed for this server.The active task's error path contains no OAuth
AuthClientwrapper, which is consistent with a previously initialized static bearer/fallback transport. It did not re-read the updated Keychain credential or recreate the transport after the successful refresh.Expected behavior:
codex mcp login <server>completes, active MCP clients should invalidate/reinitialize their transport and retry a read-only operation once.Auth required.Context: OAuth metadata discovery had been temporarily unavailable earlier in this task and was later restored. That likely explains the initial fallback selection, but the demonstrated defect is that the already-running task does not recover after valid credentials are available.
I reproduced this against current
mainand prepared a targeted fix here:https://github.com/smalljimmy/codex/tree/codex/mcp-auth-hot-reload
Root cause:
RmcpClientalready retains a transport recipe that rereads the pinned OAuth credential store when rebuilding, but live-operation recovery only rebuilt for session-expired 404s.AuthRequired,AuthorizationRequired, andTokenExpiredtransport failures therefore left the running service attached to its old in-memory OAuth manager even after another process (for example,codex mcp login) replaced the persisted credentials.The patch reuses the existing serialized, per-client transport recovery path for those authentication-required failures and retries the failed operation exactly once. It does not poll the credential store, restart unrelated MCP servers, or change the existing behavior for a plain 401 without a
WWW-Authenticatechallenge.Regression coverage creates a live OAuth MCP client with an initial token, replaces the persisted credential as a separate login process would, makes the initial
tools/callfail with a Bearer challenge, and verifies the same client rebuilds and retries with the replacement token. The test failed withAuth requiredbefore the production change and passes afterward.Verification:
just test -p codex-rmcp-client: 111 passed, 6 skippedjust fix -p codex-rmcp-clientjust fmtCommit: https://github.com/smalljimmy/codex/commit/006f9bd6c7
The contribution guide says external PRs require an explicit maintainer invitation. If this approach matches the intended solution, please invite the PR and I will open it immediately.
+1. Please start the integration process on this. It's a critical flaw that prevents using Codex MCP.