Summary
We operate a remote MCP server (streamable HTTP) with an OAuth 2.1 issuer (rotating refresh tokens, S256 PKCE, RFC 8414/8707/9728, CIMD client support). On 2026-07-25, Codex CLI failed every tool call against this server on two devices simultaneously with:
tool call error: tool call failed for `codex_apps/<server>.<tool>`
Caused by:
Mcp error: -32603: Internal error
— including zero-argument tools (whoami). codex mcp login <server> on the affected device resolved it.
We investigated from the server side before finding the client-side cause, and are reporting because the server-side evidence shows two distinct issues that user-side reports probably can't demonstrate.
Server-side evidence
Failure window ≈ 11:00–11:50 UTC, 2026-07-25.
- The failing requests never left the client. Worker request logs (100% sampling) show zero requests from the
openai-mcp/1.0.0 (ChatGPT) user agent to the MCP endpoint during the window. Before and after it, requests from that UA return 200. No 401/403 was issued to any OpenAI-related UA at any point; no token-endpoint failures either.
- The refresh chain kept rotating successfully during the outage. The account's grant shows successful refresh rotations at 10:05:52Z and 11:01:37Z — from OpenAI backend user agents (
python-httpx/0.28.1, Python/3.12 aiohttp/3.13.5) — while both devices were failing.
- All surfaces share one brokered token chain. The grant's refresh-token lineage is a single, perfectly linear rotation chain (25 rotations over 8 days, one root, no forks) covering two Codex CLI devices and the ChatGPT connector. The
client_id is the CIMD document https://chatgpt.com/oauth/2Dpr-r7e86Dn/client.json, and only backend UAs ever hit /oauth/token — so token state appears to be held and refreshed server-side by OpenAI, shared across the user's devices.
codex mcp login did not re-authorize. The recovery at 11:52:45Z created no new authorization (no consent, no new chain root); it continued the existing chain as an ordinary refresh rotation.
The two issues
1. Shared token state goes stale and does not self-recover. With one rotating refresh chain shared across all of a user's devices/surfaces, some holder's view of the current token went stale (the chain had moved on at 10:05Z/11:01Z) and stayed broken until a manual codex mcp login — which itself just performed a refresh, i.e. the credential was recoverable all along. Whatever holds the brokered state could presumably re-fetch/retry instead of entering a terminal state. Note that OAuth 2.1 §4.3.1 reuse detection makes this failure mode sharp for servers to tolerate: a holder more than one rotation behind that does present its stale token would trip theft detection and revoke the credential family, so client-side serialization of refreshes on a shared chain matters. Possibly related: #26586.
2. The failure surfaces as -32603: Internal error instead of an auth error. Every tool call fails with a generic JSON-RPC internal error, indistinguishable from a server-side fault — the message even says the tool call failed, though no request was ever dispatched. An OAuth refresh failure should surface as an authentication problem (re-auth prompt, as on initial 401), not as an internal error. This rendering cost us a full server-side investigation (logs, token-table forensics) to rule out our own infrastructure before concluding the requests never arrived.
Environment
- codex-cli 0.144.0, macOS (both devices)
- Server: streamable HTTP MCP, stateless POST transport; OAuth 2.1 issuer with rotating refresh tokens (~1h access / ~30d refresh TTL), single-step rotation grace
Happy to provide redacted log excerpts or timestamps in more detail.
Summary
We operate a remote MCP server (streamable HTTP) with an OAuth 2.1 issuer (rotating refresh tokens, S256 PKCE, RFC 8414/8707/9728, CIMD client support). On 2026-07-25, Codex CLI failed every tool call against this server on two devices simultaneously with:
— including zero-argument tools (
whoami).codex mcp login <server>on the affected device resolved it.We investigated from the server side before finding the client-side cause, and are reporting because the server-side evidence shows two distinct issues that user-side reports probably can't demonstrate.
Server-side evidence
Failure window ≈ 11:00–11:50 UTC, 2026-07-25.
openai-mcp/1.0.0 (ChatGPT)user agent to the MCP endpoint during the window. Before and after it, requests from that UA return 200. No 401/403 was issued to any OpenAI-related UA at any point; no token-endpoint failures either.python-httpx/0.28.1,Python/3.12 aiohttp/3.13.5) — while both devices were failing.client_idis the CIMD documenthttps://chatgpt.com/oauth/2Dpr-r7e86Dn/client.json, and only backend UAs ever hit/oauth/token— so token state appears to be held and refreshed server-side by OpenAI, shared across the user's devices.codex mcp logindid not re-authorize. The recovery at 11:52:45Z created no new authorization (no consent, no new chain root); it continued the existing chain as an ordinary refresh rotation.The two issues
1. Shared token state goes stale and does not self-recover. With one rotating refresh chain shared across all of a user's devices/surfaces, some holder's view of the current token went stale (the chain had moved on at 10:05Z/11:01Z) and stayed broken until a manual
codex mcp login— which itself just performed a refresh, i.e. the credential was recoverable all along. Whatever holds the brokered state could presumably re-fetch/retry instead of entering a terminal state. Note that OAuth 2.1 §4.3.1 reuse detection makes this failure mode sharp for servers to tolerate: a holder more than one rotation behind that does present its stale token would trip theft detection and revoke the credential family, so client-side serialization of refreshes on a shared chain matters. Possibly related: #26586.2. The failure surfaces as
-32603: Internal errorinstead of an auth error. Every tool call fails with a generic JSON-RPC internal error, indistinguishable from a server-side fault — the message even says the tool call failed, though no request was ever dispatched. An OAuth refresh failure should surface as an authentication problem (re-auth prompt, as on initial 401), not as an internal error. This rendering cost us a full server-side investigation (logs, token-table forensics) to rule out our own infrastructure before concluding the requests never arrived.Environment
Happy to provide redacted log excerpts or timestamps in more detail.