You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is an umbrella/tracking issue for the end-to-end MCP OAuth lifecycle. It does not replace the narrower bug reports linked below.
Codex has already landed important foundations: shared credential-store locking and pinning (#30292 and #30293), serialized proactive refresh before startup and public operations (#32229), typed reauthentication signaling (#29877 and #30257), and runtime reconciliation primitives (#34930, #34952, and #34957).
The remaining problem is that these pieces do not yet produce one predictable recovery workflow across plugin projection, CLI/Desktop/IDE runtimes, refresh failure classification, interactive reauthorization, and login/logout.
This is especially important in enterprise environments where many MCP servers sit behind corporate SSO, access tokens are intentionally short-lived, refresh tokens rotate, and several Codex processes or surfaces may share the same credential authority.
Claude Code documents that MCP OAuth tokens are stored securely and refreshed automatically. Teams evaluating migrations expect similarly predictable behavior from Codex; this issue does not require the same implementation, only equivalent lifecycle reliability:
#32229 performs a serialized read-refresh-write transaction, rereads the authoritative store after locking, adopts a winner from another process, persists token rotation before use, and runs refresh before MCP initialize and public operations.
#34930, #34952, and #34957 centralize MCP runtime state and provide reconciliation, connection reuse, and replacement primitives.
These are substantial improvements. The remaining gaps below should be framed as coverage and lifecycle integration on top of those foundations, not as if automatic refresh or reconciliation infrastructure were completely absent.
Remaining lifecycle gaps
1. Do not silently omit plugin MCPs
Managed plugin requirements that do not declare an MCP allowlist should not be treated as an empty allowlist.
This exact bug is tracked by #34110. The broader silent tool-omission symptom is also reported in #18527. An explicitly empty allowlist should continue to deny plugin MCPs; an absent MCP policy should not.
There is a second registry-parity problem in #34859: the running runtime can materialize an account-synced plugin MCP while a standalone codex mcp login <name> process cannot resolve the same server. Runtime, status, and management commands should use the same effective MCP registry.
A configured server should be mounted or expose a precise policy, registry, authentication, or startup reason. Policy-disabled state must not be presented as Auth: Unsupported.
2. Preserve the landed automatic-refresh guarantees across every path
The baseline proactive refresh transaction is already implemented by #32229. The remaining requirement is to ensure that routed/plugin/Desktop paths and 401 recovery do not bypass or retain stale copies of that authority.
Open symptoms include #17265 and #27165. Direct-keyring concurrency regression coverage is tracked by #33540. Codex-owned one-shot 401 recovery and session rebuild remain proposed in #30294.
3. Classify refresh failures by remediation
The current refresh path receives RMCP AuthError::TokenRefreshFailed(String) for both definitive rejection and transient token-endpoint failures, and currently converts the entire variant to AuthorizationRequired.
The lifecycle should distinguish at least:
invalid_grant: the refresh token/grant is invalid, expired, or revoked; require a new authorization grant.
network failures, timeouts, HTTP 5xx, and provider-declared temporary failures: preserve the durable credential authority, report a retryable failure, and do not immediately open OAuth.
persistence failure after the provider may have rotated the token: treat the outcome as unknown, fail closed, reread the authoritative store, and avoid a blind replay or silently switching credential stores.
Earlier PRs #26519 and #26237 described part of this distinction but closed without merge.
4. Recognize authentication-required errors through every wrapper
Authentication-required state must survive all RMCP transport/error wrappers and reach clients as a typed reason.
The current nested classifier recognizes StreamableHttpError::Auth(AuthError::AuthorizationRequired | TokenExpired) but not StreamableHttpError::AuthRequired(...).
6. Reconcile credentials without requiring an app restart
The expected timing differs by login path:
For an OAuth callback owned by the running app-server, invalidate and reconcile the affected runtime immediately after the credential is persisted.
For codex mcp login <server> running in another process, detect the new credential generation through store watching, IPC, or a reread/reconciliation boundary no later than the next operation or turn.
In both cases, users should not need to restart Codex or create a new conversation.
Related issue: #14144. The primitives landed in #34930, #34952, and #34957, but the successful app-server OAuth callback currently emits a completion notification without triggering runtime reconciliation.
7. Keep one authoritative credential lifecycle across surfaces
CLI, Desktop, IDE, and automations using the same Codex home should not overwrite rotated credentials, resurrect logged-out credentials, or retain stale generations independently.
#32229 substantially addresses concurrent refresh. Login and logout still need to participate in the same per-credential transaction boundary; that work is proposed in #30295. The earlier #30416 proposal is functionally represented by the merged #32229 and should not be counted as a separate missing refresh implementation.
Credential durability and store selection also need actionable diagnostics. Related reports include #15122 and the volatile Linux keyring case #34943.
8. Expose actionable lifecycle status
Status APIs and UIs should distinguish:
no authentication required;
first login required;
refreshable credential available;
reauthentication required;
retryable provider/network failure;
ambiguous refresh/persistence outcome;
policy-disabled;
configured in runtime but unavailable to management commands.
This prevents policy, projection, registry, storage, and OAuth failures from collapsing into Auth: Unsupported or a missing tool namespace.
Expected state machine
For each OAuth-backed MCP:
Valid access token: initialize normally.
Expired or near-expiry access token plus a usable refresh token: run the serialized refresh transaction and persist any rotation before exposure.
Another process already committed a newer generation: adopt the authoritative winner without contacting the provider again.
Transient provider/network failure: preserve credentials and report a retryable failure without requesting a new grant.
Provider refresh succeeded but persistence outcome is uncertain: fail closed, reconcile against the authoritative store, and do not blindly replay the previous rotating token.
invalid_client: attempt the appropriate client-registration recovery or return a distinct remediation.
Interactive surface: offer/launch OAuth and retry startup at most once.
Successful in-process callback: reconcile the affected runtime immediately.
Successful external CLI login: loaded runtimes adopt the new generation by the next operation or turn, without restart.
Headless surface: fail deterministically with a stable reconnect action and no browser dependency.
Acceptance criteria
The proactive refresh behavior landed in #32229 works consistently for startup, tool calls, routed/plugin servers, and long-lived threads.
A valid stored refresh token survives access-token expiry without user action.
invalid_grant produces an actionable reconnect flow; timeout or HTTP 5xx does not delete credentials or immediately request a new grant.
Two processes cannot replay or overwrite a rotating refresh token.
Login, refresh, and logout share the same per-credential transaction boundary.
A successful app-owned OAuth callback repairs the current thread without restart.
A successful external CLI login is adopted by loaded runtimes no later than their next MCP operation or turn.
A plugin MCP is not disabled merely because managed plugin requirements exist without an MCP allowlist.
Runtime and codex mcp list|get|login resolve the same effective plugin MCP registry.
Headless clients receive stable typed remediation rather than a browser dependency or silently missing tools.
Status surfaces expose the actual lifecycle/policy failure reason.
Operational follow-ups
These are useful, but should not be treated as substitutes for automatic lifecycle recovery:
Multi-server repair
Provide a sequential repair flow such as codex mcp login --all-required or codex mcp repair. It should operate only on servers that need first login or reauthorization, skip healthy refreshable credentials and unsupported/static auth, and never open several browser flows concurrently.
Failed-startup cleanup
If initialize or initial list_tools fails, explicitly shut down the client/transport when ownership is not already guaranteed to do so. This should have its own focused regression or resource-leak reproduction rather than being assumed to be an OAuth root cause.
Non-goals
Per-session corporate approval or step-up authorization is distinct from OAuth token refresh and should retain its own policy/consent semantics.
The request is not to open a browser for every MCP at startup.
Static bearer-token and stdio authentication are not OAuth refresh flows.
This issue does not require Codex to copy Claude Code's internal implementation.
Why keep an umbrella issue
The linked issues and PRs correctly address individual layers, but enterprise users experience the combined failure as one outcome: an MCP that was previously authorized stops being available and does not recover predictably.
A lifecycle owner and an end-to-end regression suite covering CLI, app-server, plugin registries, concurrent processes, rotating refresh tokens, transient provider failures, and active-runtime reconciliation would ensure the landed pieces compose into a dependable corporate workflow.
Summary
This is an umbrella/tracking issue for the end-to-end MCP OAuth lifecycle. It
does not replace the narrower bug reports linked below.
Codex has already landed important foundations: shared credential-store
locking and pinning (#30292 and #30293), serialized proactive refresh before
startup and public operations (#32229), typed reauthentication signaling
(#29877 and #30257), and runtime reconciliation primitives (#34930, #34952,
and #34957).
The remaining problem is that these pieces do not yet produce one predictable
recovery workflow across plugin projection, CLI/Desktop/IDE runtimes, refresh
failure classification, interactive reauthorization, and login/logout.
This is especially important in enterprise environments where many MCP
servers sit behind corporate SSO, access tokens are intentionally short-lived,
refresh tokens rotate, and several Codex processes or surfaces may share the
same credential authority.
Claude Code documents that MCP OAuth tokens are stored securely and refreshed
automatically. Teams evaluating migrations expect similarly predictable
behavior from Codex; this issue does not require the same implementation, only
equivalent lifecycle reliability:
https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers
Current upstream state
The observations below were rechecked against
mainat62ba648136c7e60b9380c40b60cb553a7d8eb1ab.Already landed
store for the client lifecycle.
authoritative store after locking, adopts a winner from another process,
persists token rotation before use, and runs refresh before MCP initialize
and public operations.
typed
reauthenticationRequiredreason for the wrappers they recognize.reconciliation, connection reuse, and replacement primitives.
These are substantial improvements. The remaining gaps below should be framed
as coverage and lifecycle integration on top of those foundations, not as if
automatic refresh or reconciliation infrastructure were completely absent.
Remaining lifecycle gaps
1. Do not silently omit plugin MCPs
Managed plugin requirements that do not declare an MCP allowlist should not be
treated as an empty allowlist.
This exact bug is tracked by #34110. The broader silent tool-omission symptom
is also reported in #18527. An explicitly empty allowlist should continue to
deny plugin MCPs; an absent MCP policy should not.
There is a second registry-parity problem in #34859: the running runtime can
materialize an account-synced plugin MCP while a standalone
codex mcp login <name>process cannot resolve the same server. Runtime,status, and management commands should use the same effective MCP registry.
A configured server should be mounted or expose a precise policy, registry,
authentication, or startup reason. Policy-disabled state must not be presented
as
Auth: Unsupported.2. Preserve the landed automatic-refresh guarantees across every path
The baseline proactive refresh transaction is already implemented by #32229.
The remaining requirement is to ensure that routed/plugin/Desktop paths and
401 recovery do not bypass or retain stale copies of that authority.
Open symptoms include #17265 and #27165. Direct-keyring concurrency regression
coverage is tracked by #33540. Codex-owned one-shot 401 recovery and session
rebuild remain proposed in #30294.
3. Classify refresh failures by remediation
The current refresh path receives RMCP
AuthError::TokenRefreshFailed(String)for both definitive rejection andtransient token-endpoint failures, and currently converts the entire variant
to
AuthorizationRequired.The lifecycle should distinguish at least:
invalid_grant: the refresh token/grant is invalid, expired, or revoked;require a new authorization grant.
invalid_client: recover or re-register the OAuth client when possiblebefore asking for a new user grant; see MCP OAuth: Codex does not re-register (DCR) on invalid_client / expired refresh token — requires manual re-auth #29630.
failures: preserve the durable credential authority, report a retryable
failure, and do not immediately open OAuth.
outcome as unknown, fail closed, reread the authoritative store, and avoid a
blind replay or silently switching credential stores.
Earlier PRs #26519 and #26237 described part of this distinction but closed
without merge.
Relevant OAuth semantics:
4. Recognize authentication-required errors through every wrapper
Authentication-required state must survive all RMCP transport/error wrappers
and reach clients as a typed reason.
The current nested classifier recognizes
StreamableHttpError::Auth(AuthError::AuthorizationRequired | TokenExpired)but not
StreamableHttpError::AuthRequired(...).Related merged work: #29877 and #30257.
5. Provide an actionable reauthorization flow
When a refresh is definitively rejected:
and retry startup at most once;
reconnect action without depending on a browser.
Related issues: #13956 and #29630. Related open work: #30294.
6. Reconcile credentials without requiring an app restart
The expected timing differs by login path:
reconcile the affected runtime immediately after the credential is
persisted.
codex mcp login <server>running in another process, detect the newcredential generation through store watching, IPC, or a reread/reconciliation
boundary no later than the next operation or turn.
In both cases, users should not need to restart Codex or create a new
conversation.
Related issue: #14144. The primitives landed in #34930, #34952, and #34957,
but the successful app-server OAuth callback currently emits a completion
notification without triggering runtime reconciliation.
7. Keep one authoritative credential lifecycle across surfaces
CLI, Desktop, IDE, and automations using the same Codex home should not
overwrite rotated credentials, resurrect logged-out credentials, or retain
stale generations independently.
#32229 substantially addresses concurrent refresh. Login and logout still need
to participate in the same per-credential transaction boundary; that work is
proposed in #30295. The earlier #30416 proposal is functionally represented by
the merged #32229 and should not be counted as a separate missing refresh
implementation.
Credential durability and store selection also need actionable diagnostics.
Related reports include #15122 and the volatile Linux keyring case #34943.
8. Expose actionable lifecycle status
Status APIs and UIs should distinguish:
This prevents policy, projection, registry, storage, and OAuth failures from
collapsing into
Auth: Unsupportedor a missing tool namespace.Expected state machine
For each OAuth-backed MCP:
serialized refresh transaction and persist any rotation before exposure.
authoritative winner without contacting the provider again.
retryable failure without requesting a new grant.
closed, reconcile against the authoritative store, and do not blindly
replay the previous rotating token.
invalid_grant: emit typed reauthentication-required state.invalid_client: attempt the appropriate client-registration recovery orreturn a distinct remediation.
the next operation or turn, without restart.
no browser dependency.
Acceptance criteria
startup, tool calls, routed/plugin servers, and long-lived threads.
action.
invalid_grantproduces an actionable reconnect flow; timeout or HTTP 5xxdoes not delete credentials or immediately request a new grant.
boundary.
restart.
their next MCP operation or turn.
exist without an MCP allowlist.
codex mcp list|get|loginresolve the same effective plugin MCPregistry.
dependency or silently missing tools.
Operational follow-ups
These are useful, but should not be treated as substitutes for automatic
lifecycle recovery:
Multi-server repair
Provide a sequential repair flow such as
codex mcp login --all-requiredorcodex mcp repair. It should operate only on servers that need first login orreauthorization, skip healthy refreshable credentials and unsupported/static
auth, and never open several browser flows concurrently.
Failed-startup cleanup
If
initializeor initiallist_toolsfails, explicitly shut down theclient/transport when ownership is not already guaranteed to do so. This
should have its own focused regression or resource-leak reproduction rather
than being assumed to be an OAuth root cause.
Non-goals
OAuth token refresh and should retain its own policy/consent semantics.
implementation.
Why keep an umbrella issue
The linked issues and PRs correctly address individual layers, but enterprise
users experience the combined failure as one outcome: an MCP that was
previously authorized stops being available and does not recover predictably.
A lifecycle owner and an end-to-end regression suite covering CLI, app-server,
plugin registries, concurrent processes, rotating refresh tokens, transient
provider failures, and active-runtime reconciliation would ensure the landed
pieces compose into a dependable corporate workflow.