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_

View original on GitHub ↗

21 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #15754
  • #15502

Powered by Codex Action

infoseekAI · 3 months ago

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.

ankitvg · 3 months ago

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:

  • I connected the same MCP server (stewreads.com) from both ChatGPT app and Codex app.
  • Codex app creates its own OAuth client during setup.
  • But server logs show Codex app requests carrying token claims (client_id / aud) for the ChatGPT client.

Control test:

  • I ran Codex CLI inside the Codex app terminal against the same MCP server.
  • Codex CLI used the correct Codex client-issued token.
  • So this seems specific to Codex App’s built-in MCP runtime path, not Codex CLI.

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/aud maps to ChatGPT client.
5) Run Codex CLI against same server and compare: client_id/aud maps correctly to Codex client.

Expected:

  • Codex App should use only Codex client-issued tokens (app/client-scoped credential isolation).

Actual:

  • Codex App appears to reuse ChatGPT client token for the same MCP server identity.
Cyanoth · 2 months ago

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

  1. Add an MCP server authenticated via OAuth to Codex.
  2. Complete the OAuth flow to obtain a valid access token and refresh token.
  3. In the Codex desktop app, use the MCP server successfully in a chat.
  4. Wait until the access token expires.
  5. In the same chat, send another request that uses the same MCP server.

Actual result

  1. The MCP server responds with error: Auth required.
  2. The desktop app appears to continue using the expired access token.
  3. Retrying in the same chat does not recover authentication.
  4. Starting a new chat makes the MCP server work again.
  5. This suggests the existing chat session does not trigger token refresh or pick up refreshed auth state.

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.

technotablet · 2 months ago

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.

amiranvarov · 2 months ago

i'm on v0.128.0, and it still has this issue

dsghi · 2 months ago

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

  • Product: Codex Desktop on Windows 26.429.61741
  • MCP auth: OAuth / Dynamic Client Registration
  • Access token lifetime: 10 minutes
  • Refresh token lifetime: 30 days
  • Server behavior: refresh tokens rotate on use
  • Server mitigation already tried: a 5-minute idempotent replay marker for the immediately previous refresh token

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:

19:18  a577bd5762f9 -> 2c038f3caf9e
19:27  2c038f3caf9e -> cf1e044b93dc
19:39  cf1e044b93dc -> 432dc8317a97
19:48  432dc8317a97 -> 70a8eba10384

Then an older/stale Codex session attempted MCP calls:

19:55  rejected reason=missing refreshTokenFingerprint=2c038f3caf9e

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

  • The MCP server is healthy and accepts current refresh tokens.
  • A different Codex session in the same app instance can continue to use the MCP server successfully.
  • The failing session presents an older refresh token generation instead of the latest one.
  • Starting/switching sessions can temporarily restore functionality, but older sessions later fail after the access token's expiry.
  • The behavior matches the report in #14144: active sessions continue using stale refresh credentials.

Expected Behavior

When one Codex session refreshes an OAuth MCP token, other sessions using the same MCP server should either:

  • read the latest app-level credential state before attempting refresh, or
  • invalidate/reload their per-session MCP auth cache, or
  • recover cleanly after invalid_grant by 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.

w00ing · 2 months ago

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-auth directory
Observed on 2026-05-12:

  • codex mcp login mixpanel had previously succeeded.
  • The stored MCP OAuth payload contained both access_token and refresh_token.
  • The stored token response had expires_in: 86400; local expires_at indicated the access token should still have been valid.
  • A direct MCP initialize request using the stored access token returned 401 invalid_token from Mixpanel. The error description said the bearer token was invalid, expired, or no longer recognized, and recommended clearing/reconnecting MCP client auth.
  • A direct OAuth refresh-token request to Mixpanel succeeded using the stored refresh_token and returned a fresh access token plus a fresh refresh token.
  • The freshly refreshed access token then succeeded against the same Mixpanel MCP initialize request with 200 OK.
  • Running codex mcp login mixpanel again 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.

toxaus · 2 months ago

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:

22:28:00 (×9 simultaneous calls)  tok_fp=25a4fabf... (token already expired)
23:15:31 (×10 simultaneous calls) tok_fp=25a4fabf... (47 min later, SAME token)

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:

HTTP/2 401
WWW-Authenticate: Bearer realm="mcp", error="invalid_token",
                  error_description="The access token expired",
                  resource_metadata="https://my.jetoctopus.com/.well-known/oauth-protected-resource"
{"jsonrpc":"2.0","error":{"code":-32001,
  "message":"Access token expired. Refresh via /oauth/token using your refresh_token and retry.",
  "data":{"reason":"token_expired",
          "remediation":"refresh_via_oauth_token",
          "refresh_endpoint":"https://my.jetoctopus.com/oauth/token"}},
 "id":null}

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.

nimz · 1 month ago

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:

  • Codex CLI: 0.137.0
  • Auth mode: ChatGPT
  • MCP server: notion -> https://mcp.notion.com/mcp
  • Config:
  • mcp_oauth_credentials_store = "file"
  • [mcp_servers.notion]

url = "https://mcp.notion.com/mcp"
oauth_resource = "https://mcp.notion.com"

Observed:

  • After codex mcp login notion, Notion tools are available.
  • ~/.codex/.credentials.json contains a Notion entry with both access_token and refresh_token.
  • The Notion access token expired at 2026-06-05 01:03:15 PDT.
  • The credentials file was last modified at 2026-06-05 00:03:15 PDT, so Codex did not refresh it after expiry.
  • After expiry, Notion tools are no longer exposed/discoverable.
  • codex mcp list still shows Notion configured and enabled with OAuth.
  • Logs show Codex waiting for Notion tools after expiry, but not listing them; nearby MCP auth failure includes:

Auth required, when send initialize request

Expected:
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 notion generated an OAuth authorize URL containing both:

  • resource=https://mcp.notion.com/mcp
  • resource=https://mcp.notion.com

That may be related to resource/audience handling for Notion MCP.

drkvj · 1 month ago

Adding another sanitized confirmation with a controlled proof on current Codex.

Environment:

  • Codex CLI: 0.137.0-alpha.4
  • Platform: macOS
  • Credential store: macOS Keychain, service Codex MCP Credentials
  • MCP server shape: private Streamable HTTP MCP server using OAuth 2.1 + PKCE, short-lived JWT access tokens, long-lived refresh tokens
  • Token values, server URL, user identifiers, and Keychain account IDs intentionally omitted

Observed on two separate Macs:

  1. codex mcp login <server> succeeds and a Codex MCP Credentials Keychain item exists.
  2. The stored credential contains both access_token and refresh_token plus an expires_at timestamp.
  3. Once the stored access token expires, fresh codex exec fails during MCP startup/tool discovery with:
AuthRequired(AuthRequiredError { www_authenticate_header: "Bearer realm=..., resource_metadata=..., scope=..., error=\"invalid_token\"" })
  1. In that failed state, the MCP tools are not exposed to the session.
  2. Directly calling the MCP server token endpoint with the stored refresh token and stored client ID returns HTTP 200 and a fresh access token. The server is not rejecting the refresh token.
  3. Manually writing that fresh token response back into the same Codex MCP Credentials Keychain item makes a fresh codex exec immediately succeed and expose/call the MCP tool.

Controlled proof sequence:

T0: Keychain contains expired access token and valid refresh token.
T1: Direct POST /token with grant_type=refresh_token returns 200 and fresh access_token.
T2: Fresh codex exec immediately after direct refresh, without updating Keychain, still fails with invalid_token.
T3: Manually update the existing Keychain item with the fresh token response.
T4: Fresh codex exec succeeds and calls the MCP tool.

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_token challenge.

Expected behavior:

  • On MCP startup, read stored credential and use expires_at to refresh before initialize when expired or near expiry.
  • On 401 invalid_token, attempt refresh with the stored refresh token, persist the new token response, and retry MCP initialization/tool discovery.
  • Keep tools exposed after successful refresh.
  • codex mcp list should not show the server as Auth = Unsupported when OAuth metadata and stored OAuth credentials are present; one affected Mac showed Unsupported while another showed OAuth on 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.

radugheorman · 1 month ago

adding another data point here since this is basically what i just reproduced on current stable codex cli (0.137.0).

sanitised setup:

  • remote streamable-http MCP server using OAuth
  • credentials stored in macOS keychain under Codex MCP Credentials
  • stored credential has an expired access_token, a present refresh_token, expires_in: 3600, and expires_at in the past

what i verified:

  1. fresh codex startup fails during MCP initialize with the JsonRpcMessage deserialise error
  2. calling the MCP endpoint directly with the expired stored access token returns an auth failure, not JSON-RPC
  3. calling the OAuth token endpoint directly with the stored refresh token succeeds (HTTP 200) and returns a fresh access token
  4. calling the same MCP initialize endpoint 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:

  • check expires_at before MCP startup/tool discovery and refresh if expired / near expiry
  • on auth failure during initialize, try the stored refresh token, persist the new token response, and retry
  • if refresh fails, surface the actual OAuth/HTTP auth error instead of a JSON-RPC deserialise error
xinyu-dev · 1 month ago

I have similar issue

oppegard · 1 month ago

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:

  1. Try to remember: has it been an hour since I authenticated? If "yes":
  2. Quit Codex.app
  3. Run codex mcp login honeycomb from the terminal
  4. Launch Codex.app
  5. Query "can you see honeycomb?" in a session.

I 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.

bhudgens · 1 month ago

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.

dissonantP · 27 days ago

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.

ankitvg · 27 days ago

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.

ericyangliu · 25 days ago

Same issue I'm seeing on my end, too.

roshanjrajan · 25 days ago

https://github.com/openai/codex/pull/30089

I think this stack is trying to work on it?

nima20002000 · 6 days ago

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:

  • Ubuntu Linux, Codex CLI 0.144.3
  • Remote Streamable HTTP MCP: Linear (https://mcp.linear.app/mcp)
  • OAuth credential written to ~/.codex/.credentials.json

Observed:

  1. codex mcp login linear completed successfully multiple times.
  2. The file credential contained an access token, refresh token, read/write scopes, and a future expires_at value.
  3. A direct MCP initialize request using that exact stored access token returned HTTP 200, proving the token and provider session were valid.
  4. A fresh codex exec still failed during MCP startup with:

``text
Auth error: OAuth authorization required, when send initialize request
``

  1. I also removed a duplicate curated Linear plugin registration and kept only the direct [mcp_servers.linear] entry. The failure persisted, so duplicate registration was not the root cause.
  2. Starting the same fresh read-only probe with this override immediately succeeded and exposed the Linear tools:

``text
-c 'mcp_oauth_credentials_store="file"'
``

  1. Persisting the following setting in ~/.codex/config.toml fixed 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.

stevenlee-oai contributor · 2 days ago

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:

  • Codex Desktop production: 26.715.21316 or newer, first released July 16, 2026 at approximately 23:27 UTC. Versions starting with 26.707 or older do not contain the complete fix.
  • Codex CLI/harness alpha: 0.145.0-alpha.3 or newer, first released July 10, 2026 at approximately 23:11 UTC. Alpha builds are publicly available.
  • Codex CLI/harness stable: the current 0.144.6 release does not contain the complete fix. The fixes are expected in the upcoming 0.145 stable 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.