Codex does not auto-refresh routed MCP OAuth tokens even when a refresh token is stored
Open 💬 21 comments Opened Apr 9, 2026 by infoseekAI
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
What issue are you seeing?
Summary
Codex persists a refresh_token for a routed MCP server in ~/.codex/.credentials.json, but it does not automatically refresh the access token when the token expires.
As a result, once the access token expires:
- MCP tool calls start failing with auth errors
- automations and later runs break
- the only recovery is manual CLI login and browser reauth
This defeats the purpose of storing a refresh token.
Environment
- Product: Codex desktop / Codex MCP client. MacOS 26.3.1 (a)
- Date observed: April 9, 2026
Expected behavior
If Codex has a valid stored refresh_token for the MCP server, it should automatically refresh the expired access token and continue making authenticated MCP calls without requiring manual CLI login.
This is especially important for:
- automations
- recurring background runs
- later thread/tool invocations after some idle time
Actual behavior
When the access token expires, Codex starts returning auth failures instead of using the stored refresh token to recover automatically.
Observed failure:
{"success": false, "error": "invalid_grant", "msg": "Authorization required"}
### What steps can reproduce the bug?
- Auth to MCP, observe the credentials and refresh token stored in credentials.json
- Wait for the token expiry, access the MCP again (prior to refresh token expiry), it will fail on auth and not automatically refresh as it should
- Use the CLI to authenticate manually, it will then work again
### What is the expected behavior?
Within the refresh token expiry window re-auth should be automatic
### Additional information
_No response_
21 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Yes. In practical terms, OAuth-backed MCPs are dead in Codex right now.
Not because provider auth is broken, but because Codex’s built-in MCP execution path is failing to use valid stored OAuth credentials. The proof is already clear:
codex mcp login succeeds
/Users/xyz/.codex/.credentials.json contains valid tokens
direct refresh works
direct authenticated MCP calls work
built-in MCP tool calls still return 401 oauth_required
So from the user’s perspective, the result is the same: any MCP that depends on OAuth is unusable in Codex until that runtime layer is fixed.
Adding a concrete datapoint that seems related but more specific than token refresh:
In my environment, Codex App (macOS) appears to send MCP requests using a token issued to the ChatGPT OAuth client, not the Codex OAuth client.
What I observed:
stewreads.com) from both ChatGPT app and Codex app.client_id/aud) for the ChatGPT client.Control test:
Repro:
1) Connect MCP server in ChatGPT app and complete OAuth.
2) Connect same MCP server in Codex app and complete OAuth.
3) Invoke MCP tools from Codex app.
4) Inspect token claims in MCP server logs:
client_id/audmaps to ChatGPT client.5) Run Codex CLI against same server and compare:
client_id/audmaps correctly to Codex client.Expected:
Actual:
We run a few MCP Servers and our users are reporting the same with the codex desktop app (both windows & mac)
In the Codex desktop app, MCP servers authenticated via OAuth stop working after the access token expires. The app appears to keep using the expired access token for the current chat instead of refreshing it with the refresh token.
Starting a new chat restores functionality, and manually restarting the MCP server also restores it temporarily. The Codex CLI does not have this issue & handles token refresh correctly.
Codex Desktop App (Version 26.415.30602 (1773))
MCP Auth type: OAuth Token lifetime: access token = 1 hour, refresh token = 30 days
Steps to reproduce
Actual result
Expected result
When the access token expires, the desktop app should automatically refresh it using the OAuth refresh token and continue making MCP requests seamlessly.
This is the behavior currently observed in codex CLI.
Workaround
In the Codex desktop app:
Go to Settings -> MCP Servers -> Toggle the affected MCP server off and on. Click the Restart button
After restarting, MCP calls work again in the chat, but only until the access token expires again.
Impact
This breaks long-lived chats that rely on OAuth-authenticated MCP servers. Users must either start a new chat or manually restart the server after token expiry.
I presume Codex v0.128.0 no longer has this issue.
I was able to login to the MCP server (todoist), continued the same codex session but with a 3-4 hour inactivity (the access token typically expires in 1 hour), and also started a fresh session the next day. All cases that never worked earlier worked this time.
i'm on v0.128.0, and it still has this issue
I’m seeing what appears to be the same issue in Codex Desktop on Windows, with server-side evidence from an OAuth-enabled MCP server.
Related: #14144
Summary
Codex Desktop sessions appear to retain per-session MCP OAuth refresh-token state. When multiple Codex sessions use the same OAuth MCP server, one session may rotate the refresh token chain forward while an older session later resumes and presents a refresh token from several generations back.
The older session then fails all MCP operations with generic errors until the MCP server is disabled/re-enabled or the session/app is restarted.
Environment
Server-side Evidence
The MCP server logs short SHA-256 fingerprints of refresh tokens, not token material.
Successful refresh chain for the same Codex MCP client ID:
Then an older/stale Codex session attempted MCP calls:
That token had been valid at 19:27 but was already multiple generations behind by 19:55. Because it was not the immediately previous token, the server’s replay window could not recover it.
The stale session then repeatedly failed reads/searches/writes with
invalid_grant, while another Codex session in the same desktop app instance could still read/write successfully.Why This Looks Codex-Side
Expected Behavior
When one Codex session refreshes an OAuth MCP token, other sessions using the same MCP server should either:
invalid_grantby reloading the current credential state / prompting reauth.Actual Behavior
Older sessions continue to present stale refresh tokens and surface generic MCP failures.
Suggested Fix Area
Codex Desktop’s MCP OAuth runtime likely needs shared credential synchronization or cache invalidation across sessions/chats for the same MCP server. Refresh-token rotation makes stale per-session credential copies invalid by design.
Adding another datapoint that matches this issue, with Mixpanel's hosted MCP server.
Environment:
Codex CLI: codex-cli 0.130.0
Platform: macOS 26.3.1 (a), Darwin 25.3.0 arm64
MCP server: mixpanel, streamable_http, https://mcp.mixpanel.com/mcp
Credential store: macOS Keychain item Codex MCP Credentials, not a local
.mcp-authdirectoryObserved on 2026-05-12:
codex mcp login mixpanelhad previously succeeded.access_tokenandrefresh_token.expires_in: 86400; localexpires_atindicated the access token should still have been valid.invalid_tokenfrom Mixpanel. The error description said the bearer token was invalid, expired, or no longer recognized, and recommended clearing/reconnecting MCP client auth.refresh_tokenand returned a fresh access token plus a fresh refresh token.codex mcp login mixpanelagain restored the stored Keychain credential and MCP access temporarily.So in this case the provider-side refresh path appears healthy: the refresh token was present and usable, and the refreshed access token worked. The failure mode is that Codex continued with an invalid/stale MCP access token instead of using the stored refresh token and updating the persisted rotated credential. This also looks sensitive to refresh-token rotation, since the refresh response includes a new refresh token.
Hitting this exactly with our self-hosted MCP server (JetOctopus). Adding a concrete data point with log evidence.
Codex Desktop: 26.519.22136 (Windows)
MCP server: OAuth 2.1 + PKCE, JWT access tokens with 1h TTL, refresh tokens stored client-side
Symptom: After the 1h JWT expires, every subsequent tool call within the same Codex chat returns 401. Codex never uses the stored refresh token to re-authenticate transparently. Confirmed via server logs that Codex retries with the same cached expired JWT for hours:
Both batches were parallel fan-outs from a single user prompt. Identical token fingerprint across the two batches, ~47 minutes apart, proves the cached credential is never refreshed.
Our server returns RFC 6750-compliant signals on expiry:
Per RFC 6750 §3.1, the error="invalid_token" parameter combined with the WWW-Authenticate header is the standard signal for OAuth 2.0 clients to refresh and retry. Codex ignores it.
Workaround that works: Starting a new chat within the same Codex Desktop window triggers a fresh MCP session initialize, which picks up a freshly-refreshed JWT. The chat session is the cache boundary.
Suggested fix: Either (a) check exp claim of cached JWT before each tool call and refresh proactively when <5 min remaining, or (b) honor RFC 6750 WWW-Authenticate: Bearer error="invalid_token" by refreshing via the stored refresh_token and transparently retrying the failed request.
Cross-referencing past work: PR openai/codex#6209 attempted this fix in Nov 2025 and was redirected to upstream modelcontextprotocol/rust-sdk#509, which merged. Codex PR #6574 also landed the rmcp-transport variant in Nov 2025. The symptom in #17265 (filed Apr 9 2026, ~5 months later) and what I'm reporting here on 26.519.22136 strongly suggests a regression — possibly introduced by the OAuth routing refactors in PRs #19064 / #19797 / #20265.
I see the same issue. This effectively makes OAuth-based MCPs unusable. More details below:
Notion MCP OAuth refresh fails in Codex even though a refresh token is stored
Environment:
url = "https://mcp.notion.com/mcp"
oauth_resource = "https://mcp.notion.com"
Observed:
codex mcp login notion, Notion tools are available.~/.codex/.credentials.jsoncontains a Notion entry with both access_token and refresh_token.codex mcp liststill shows Notion configured and enabled with OAuth.Auth required, when send initialize requestExpected:
Codex should use the stored refresh token to refresh the Notion MCP access token automatically before/during MCP initialization, without requiring daily
browser reauth.
Actual:
Codex behaves as logged out after the one-hour access token expires, despite a stored refresh token.
Impact:
This blocks normal work because Notion MCP must be manually reauthenticated every day.
Additional possible related issue:
When setting
oauth_resource = "https://mcp.notion.com",codex mcp login notiongenerated an OAuth authorize URL containing both:resource=https://mcp.notion.com/mcpresource=https://mcp.notion.comThat may be related to resource/audience handling for Notion MCP.
Adding another sanitized confirmation with a controlled proof on current Codex.
Environment:
0.137.0-alpha.4Codex MCP CredentialsObserved on two separate Macs:
codex mcp login <server>succeeds and aCodex MCP CredentialsKeychain item exists.access_tokenandrefresh_tokenplus anexpires_attimestamp.codex execfails during MCP startup/tool discovery with:Codex MCP CredentialsKeychain item makes a freshcodex execimmediately succeed and expose/call the MCP tool.Controlled proof sequence:
Conclusion:
Codex can read and use the Keychain credential when the stored access token is fresh. The refresh token is valid and accepted by the MCP server. The failure appears to be that Codex does not refresh and persist the OAuth token response before MCP startup/tool discovery, nor recover by refreshing after the
401 invalid_tokenchallenge.Expected behavior:
expires_atto refresh beforeinitializewhen expired or near expiry.401 invalid_token, attempt refresh with the stored refresh token, persist the new token response, and retry MCP initialization/tool discovery.codex mcp listshould not show the server asAuth = Unsupportedwhen OAuth metadata and stored OAuth credentials are present; one affected Mac showedUnsupportedwhile another showedOAuthon the same CLI version.This is distinct from refresh-token rotation/server rejection: direct refresh returns HTTP 200, and manual Keychain replacement is sufficient to make Codex work.
adding another data point here since this is basically what i just reproduced on current stable codex cli (
0.137.0).sanitised setup:
Codex MCP Credentialsaccess_token, a presentrefresh_token,expires_in: 3600, andexpires_atin the pastwhat i verified:
initializewith theJsonRpcMessagedeserialise errorHTTP 200) and returns a fresh access tokeninitializeendpoint with that fresh access token succeeds (HTTP 200, valid JSON-RPC initialise result)so this is not a provider-side refresh-token expiry/revocation. the refresh token is valid, refresh works, and a fresh access token immediately fixes MCP initialisation.
the issue seems to be that codex is trying MCP startup/tool discovery with the expired access token instead of refreshing first. the deserialise error is secondary: codex is trying to parse the provider's auth failure response as JSON-RPC.
expected behaviour:
expires_atbefore MCP startup/tool discovery and refresh if expired / near expiryinitialize, try the stored refresh token, persist the new token response, and retryI have similar issue
I have this same issue with the Honeycomb MCP server and the token expiring after one hour. I'm a Codex.app user on Mac, and my workflow is:
codex mcp login honeycombfrom the terminalI do this multiple times per day.
I suspect I had the same issue with the Cloudflare MCP server months ago and didn't know what the problem was (Codex.app doesn't clearly surface an OAuth error and how to re-authenticate). I ended up creating an API key to use as a bearer token env var; I've done the same for Honeycomb as a workaround.
FWIW, I've checked with coworkers that use Claude Code, and they don't have to do this manual OAuth re-authentication dance.
We are seeing the same behavior.
Codex has a stored refresh token for an MCP server, but after the access token expires, subsequent MCP tool calls fail with an auth error instead of automatically refreshing within the refresh-token validity window. Running the CLI login flow manually restores access.
Expected behavior matches the report: if a valid refresh token is present, Codex should refresh the access token automatically and continue the MCP call without requiring manual browser reauthentication.
This issue is extra annoying when using Codex CLI on headless machines because you need to do the OAuth login in a browser. You can make it work (e.g. SSH port forwarding) but it's a hassle.
Its not making sense why this is not getting more attention from OpenAI. Its a massive issue when using tools with Codex. I routinely have to restart my app to force token refresh. Can someone please look into this? My workflow will thank you.
Same issue I'm seeing on my end, too.
https://github.com/openai/codex/pull/30089
I think this stack is trying to work on it?
Adding a sanitized Linux datapoint from Codex CLI
0.144.3. This looks related to the credential-store selection side of the OAuth problem, rather than provider rejection or access-token expiry.Environment:
0.144.3https://mcp.linear.app/mcp)~/.codex/.credentials.jsonObserved:
codex mcp login linearcompleted successfully multiple times.expires_atvalue.initializerequest using that exact stored access token returned HTTP 200, proving the token and provider session were valid.codex execstill failed during MCP startup with:``
text
``Auth error: OAuth authorization required, when send initialize request
[mcp_servers.linear]entry. The failure persisted, so duplicate registration was not the root cause.``
text
``-c 'mcp_oauth_credentials_store="file"'
~/.codex/config.tomlfixed subsequent fresh sessions without another OAuth login:``
toml
``mcp_oauth_credentials_store = "file"
This indicates that login wrote a usable file credential, while the default/Auto runtime path selected or read a different credential backend. It may be relevant to the replacement OAuth stack, especially #30293 and the still-open Auto-store drift diagnostics in #30296. No token values, workspace identifiers, or private data are included here.
We’ve landed the first set of fixes for concurrent MCP OAuth refreshes. Codex now serializes refreshes per MCP credential, rereads the latest stored credential after acquiring the lock, and prevents concurrent read-modify-write operations from overwriting newly refreshed tokens.
The relevant changes are:
Minimum versions containing the fixes:
26.715.21316or newer, first released July 16, 2026 at approximately 23:27 UTC. Versions starting with26.707or older do not contain the complete fix.0.145.0-alpha.3or newer, first released July 10, 2026 at approximately 23:11 UTC. Alpha builds are publicly available.0.144.6release does not contain the complete fix. The fixes are expected in the upcoming0.145stable release.If an earlier refresh race already invalidated the provider’s refresh-token family, updating cannot recover that credential, so logging into the affected MCP once more may still be necessary.
Please let us know how MCP authentication behaves after updating, including whether the issue is resolved or repeated reauthentication still occurs. If problems continue, please share whether you’re using Desktop or CLI, the version, and the affected MCP provider.
These changes should address the primary token-clobbering failure reported here. We’re continuing to work through remaining edge cases involving RMCP-initiated/background refresh and 401 recovery.