macOS: `codex mcp login` can print OAuth URL without surfacing a browser window
Summary
On macOS, codex mcp login <server> can print the OAuth authorization URL and wait for the loopback callback, but no browser window/tab becomes visible. The command does not print the documented fallback (Browser launch failed; please copy the URL above manually.), so the client appears to believe browser launch succeeded.
This creates a high-friction MCP OAuth flow: users think the command is stuck until they manually copy the one-time URL into a browser.
Environment
- macOS 26.5.1 (arm64)
codex-cli 0.148.0-alpha.9from the Codex desktop bundle
Reproduction
- Configure an OAuth-capable remote MCP server.
- Run
codex mcp login <server> --scopes <scopes>. - Observe that the terminal prints
Authorize <server> by opening this URL in your browser:and waits. - No browser window/tab is brought forward.
- Manually opening the printed URL completes OAuth and reaches the local callback successfully.
Expected
The authorization page should be visibly opened in the user's default browser, or the CLI should reliably report that opening it failed and make the manual next step explicit.
Notes
Current source calls webbrowser::open(auth_url) after printing the URL, but this observed path appears to return success without making a browser tab visible. No OAuth URL, code, state, or account data is included here.
1 Comment
Traced the call path, and the core problem is a message-contract gap: the CLI treats
webbrowser::open(...) == Okas "a browser tab is now visible", but on macOS that's not whatOkmeans — and can't be made to mean that with the API in use.The call site.
codex mcp loginprints the URL, then only prints the fallback whenopenreturnsErr:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/rmcp-client/src/perform_oauth_login.rs#L604-L621
What
Okactually means on macOS.webbrowser1.2.2 launches via the deprecated synchronous-[NSWorkspace openURLs:withApplicationAtURL:options:configuration:error:]— a deliberate choice, per the crate's own comment, because the modern completion-handler API "relies on the caller running a main run loop to actually dispatch the launch, which isn't the case for library consumers" (macos.rs @ v1.2.2). It returnsOk(())as soon as LaunchServices hands back a running-app reference. When the default browser is already running, that means "the URL was delivered to the existing process" — whether a tab surfaces and whether the app is activated is then up to the browser and the OS. SoOk= "dispatch accepted", not "window visible", and a silent no-show (particularly on newer macOS releases, where deprecated LaunchServices entry points have become less reliable about activation) never trips theErrbranch. The observed behavior — URL printed, command waits, no fallback line, manual open works — is exactly this gap.Fix outline, cheapest first:
Okcannot certify visibility, print the manual next step unconditionally, e.g. after attempting launch: "If no browser window opened, copy the URL above into your browser." One line, removes the appears-stuck failure mode on every platform, and keeps the stronger wording for the trueErrcase./usr/bin/open <url>(spawned as a child process). Theopentool runs its own run loop and handles activation on current macOS releases; its exit status is also a slightly more honest signal than the deprecated in-process API.webbrowser-rs— the crate's sync-API choice is exactly the kind of thing that decays as Apple hollows out deprecated LaunchServices paths.Worth noting the same over-trust exists at the other
webbrowser::opencall sites — TUI history links (tui/src/app/history_ui.rs#L212), onboarding auth (tui/src/onboarding/auth.rs#L1020), andlogin/src/server.rs#L187(result discarded entirely) — so fix (1) is best applied as a shared helper rather than at one call site.