Codex Desktop: MCP connector is permanently disabled with no re-authenticate affordance after a failed OAuth token refresh

Open 💬 0 comments Opened Aug 12, 2026 by david-montry-zocdoc

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.803.81509

What subscription do you have?

Enterprise

What platform is your computer?

Darwin 25.6.0 arm64 arm

What issue are you seeing?

When an MCP server's OAuth token refresh fails in a way Codex cannot parse, Codex marks that server permanently unusable and offers no way to recover from the UI. The connector's tools silently disappear, no error is surfaced in the app, and the Authenticate button does not appear. Restarting Codex Desktop does not help — the same failed refresh is retried on every launch, indefinitely.

The stored credential is never invalidated, so Codex keeps attempting a refresh that cannot succeed instead of falling back to a fresh authorization flow.

The initial failure, from ~/.codex/logs_2.sqlite:

[DEBUG] rmcp::transport::auth
  refresh_transaction{server_name=<server> refresh_reason="expiry"}:
  refresh token present, attempting refresh

[WARN] codex_rmcp_client::oauth::refresh_transaction
  refresh_transaction{server_name=<server> refresh_reason="expiry"}:
  MCP OAuth provider refresh failed
  error=OAuth token refresh failed: Failed to parse server response

[WARN] codex_rmcp_client::oauth::refresh_transaction
  MCP OAuth refresh transaction failed server_name=<server> refresh_reason="expiry"

On every subsequent launch — across two days and many app restarts — the credential loads fine, but the client is never marked ready:

[TRACE] codex_keyring_store
  mcp.runtime.refresh:new{server_name=<server>}:make_rmcp_client{server_name=<server>}:
  keyring.load success, service=Codex MCP Credentials, account=<server>|<hash>

[TRACE] codex_mcp::connection_manager::tool_catalog
  mcp.runtime.resolve_for_step:
  omitting MCP server without an exact ready client server_name=<server>

Note the asymmetry there: the keyring load succeeds, so as far as the app is concerned the connector is authenticated — which appears to be why no Authenticate button is offered. But the client is never constructed, so none of its tools are ever registered, and the connector is silently absent from every conversation.

Another OAuth MCP server configured identically on the same machine kept working throughout, so this is not a general connectivity or configuration problem — it is one connector latching into a dead state after a single failed refresh.

What steps can reproduce the bug?

  1. Configure an MCP server over HTTP with "auth": "oauth" and complete the authorization flow successfully.
  2. Let the access token reach expiry so Codex attempts a refresh using the stored refresh token.
  3. Have the token endpoint return a response Codex cannot parse as an OAuth token response. In my case it returned HTTP 500/502 with Content-Type: application/json but an HTML error page as the body:

```
HTTP/2 500
content-type: application/json

<html>
<head><title>502 Bad Gateway</title></head>
...
```

  1. Observe that the connector's tools vanish, no error appears in the UI, and no Authenticate button is offered.
  2. Restart Codex Desktop. The connector stays dead.

Any token endpoint that returns a non-JSON body under a JSON content type will reproduce step 3. This is a normal thing for a server to do on an error path — a reverse proxy or load balancer sitting in front of an OAuth server will often return its own HTML error page while passing through the upstream's content-type header. It doesn't require the token endpoint to be misconfigured, only to be briefly unhealthy at the moment a refresh happens. Since access tokens expire on the order of an hour, every user of that server gets a fresh chance to hit it.

What is the expected behavior?

A failed token refresh should be recoverable without manual keychain surgery. Any of the following would fix it:

  1. Surface the Authenticate affordance. If a refresh fails and the client cannot be constructed, treat the connector as needing authentication and show the Authenticate button, rather than presenting it as connected-but-toolless.
  2. Surface the error. The failure is currently invisible in the UI — I only found it by querying the log database. At minimum the connector should show an error state.
  3. Don't treat an unparseable refresh response as terminal. An unparseable body carries no information about whether the refresh token is still valid, so falling back to a full authorization flow is strictly safer than latching dead forever.
  4. Invalidate the stored credential when refresh fails unrecoverably, so the next launch starts a clean flow instead of replaying a dead refresh.

Being defensive here matters because Codex can't control what a third-party or self-hosted token endpoint returns on its error paths.

#####WORKAROUND#####

Deleting the stored credential from the macOS Keychain forces Codex to start a fresh authorization flow. The Authenticate button reappears immediately afterward and the connector works again.

Codex stores MCP credentials as generic passwords under the service Codex MCP Credentials, with the account name in the form <server-name>|<hash>. The hash is not derivable from the server URL, so it has to be looked up:

acct=$(security dump-keychain | grep -A4 'Codex MCP Credentials' \
  | grep '"acct"' | sed -E 's/.*="(.*)"/\1/' | grep '^<server-name>|')

security delete-generic-password -s "Codex MCP Credentials" -a "$acct"

Then restart Codex Desktop. This is the only recovery I found — no combination of restarting, toggling the server off and on, or starting new conversations had any effect.

Additional information

_No response_

View original on GitHub ↗