Add direct-keyring regression coverage for concurrent MCP OAuth refreshes
What version of Codex CLI is running?
0.144.4 when observed; 0.145.0-alpha.16 now installed (contains #32229)
What subscription do you have?
Pro; not relevant to MCP OAuth credential persistence
Which model were you using?
N/A — MCP startup and OAuth credential refresh
What platform is your computer?
Darwin 25.4.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
Terminal.app and ChatGPT desktop app (concurrent Codex processes)
Codex doctor report
Not included: the current installed build already contains #32229; this report documents an observed 0.144.4 race and proposes missing direct-keyring regression coverage.
What issue are you seeing?
Two concurrent Codex processes shared an OAuth MCP credential through macOS Keychain. On Codex 0.144.4, the standalone client successfully refreshed a rotating token, but the Keychain entry remained on the predecessor. A later app-server process loaded that stale entry and the provider rejected its refresh with invalid_grant, making the MCP server appear to lose authorization.
Observed sequence:
- The standalone process refreshed the credential successfully.
- No successful Keychain save followed that refresh, and the durable entry remained stale.
- A second Codex process later loaded the stale Keychain credential.
- Its refresh attempted to reuse the rotated predecessor and was rejected.
#32229 appears to address the production root cause by serializing each credential's read-refresh-write transaction, rereading the authoritative store after locking, persisting before exposure, and letting later processes adopt the winner. Its successful concurrency regression currently exercises the JSON file store; the equivalent successful direct-keyring path is not covered end to end.
What steps can reproduce the bug?
- Configure a remote OAuth MCP server that rotates refresh tokens and use the direct keyring credential backend.
- Start two Codex processes that share the same
CODEX_HOMEand macOS Keychain entry (for example, standalone CLI plus app-server). - Let the stored access token expire so both clients need a proactive refresh.
- Allow the first process to refresh, then let the second process refresh from the shared durable credential.
- Without serialized durable persistence, the second process can replay the rotated predecessor and receive
invalid_grant.
The proposed deterministic regression forces both clients through the real per-credential lock contention path, expects exactly one provider refresh, and verifies the second client adopts the direct-keyring winner.
What is the expected behavior?
The first process should persist the refreshed credential to the lifecycle-pinned Keychain store before exposing it. The second process should acquire the credential lock, reread Keychain, observe the unexpired winner, and adopt it without contacting the provider. No fallback file should be created.
Additional information
I prepared a focused test-only commit and would like an invitation to submit it under the repository's external-contribution policy.
- Compare: https://github.com/openai/codex/compare/main...runminglu:agent/mcp-oauth-keyring-refresh-regression
- Commit: https://github.com/runminglu/codex/commit/1724b51a025b5811cc0ed59bfd9372d8e79156c3
- Change: direct-keyring concurrent refresh winner persistence/adoption regression test
- Validation:
just test -p codex-rmcp-client(112 passed, 5 skipped),just fix -p codex-rmcp-client,just fmt, andgit diff --check
I searched the existing MCP OAuth/keyring issues and did not find one covering this exact successful direct-keyring concurrency regression gap.