Desktop sign-in routes an already-verified account to phone ENROLLMENT instead of challenging the existing MFA factor (web succeeds with identical credentials)
Summary
The Codex/ChatGPT desktop client for macOS dead-ends at auth.openai.com/add-phone for an account that already has a verified SMS factor. This is not the "SMS will not send" cluster (#25828, #29440). The problem is that the client asks for phone enrollment at all, when it should be issuing a challenge against the factor already on the account.
Environment
- ChatGPT desktop app for macOS, build
26.715.72359(bundle idcom.openai.codex). Also reproduced on26.715.31925, and an auto-update between the two did not fix it. - macOS 26.5.1 (25F80), Apple Silicon (M3 Max)
- ChatGPT Business account, Global Admin, US mobile number, no VPN, no adblock, no custom DNS
Controlled A/B that isolates the client
Same account, same machine, same identity provider, same credential, minutes apart:
| Client | Flow | Result |
|---|---|---|
| chatgpt.com (web) | Continue with Google, then passkey | Succeeds. Lands in the existing account with full history. No phone prompt at any point. |
| Desktop app | Continue with Google, same account, same passkey | Passkey succeeds, then redirects to /add-phone and cannot proceed |
Because credential, identity provider, account, and machine are all held constant, password type, MFA factor type, and IdP are eliminated as causes. The only remaining variable is the client.
A network capture of the successful web flow shows the expected sequence:
/api/accounts/password/verify 200
/api/accounts/mfa/issue_challenge 200
/api/accounts/mfa/verify 200
-> OAuth callback -> signed in
The desktop flow never reaches issue_challenge.
Corroborating detail: the client is definitely on an enrollment path
Two different errors at that screen both indicate enrollment rather than challenge:
- Entering a VoIP number returns "It looks like this is a virtual phone number (also known as VoIP). Please provide a valid, non-virtual phone number to continue." A challenge against an existing factor would never validate the type of a newly entered number.
- Entering the number that is already verified on the account returns "This phone number is already linked to the maximum number of accounts." That is the enrollment path refusing a number it should have been challenging.
Possible mechanism: split identity records after a Google Workspace domain rename
This account's Google Workspace primary domain was renamed, so Google now presents a new-domain address for the same person. On OpenAI the two addresses behave inconsistently, which suggests two distinct auth identity records:
- New-domain address via Continue with Google: resolves into the existing account and works on web.
- New-domain address via direct email sign-in: fails with
error_code: invalid_auth_step(examplerequest_id: b738c329-62c7-4a5b-b850-2353fc4223ae), and on refresh the flow offers a Create a password screen, i.e. it treats the address as a brand new signup.
So the new-domain address is a Google-linked identity with no standalone account behind it, not an alias of the primary account.
Hypothesis: the desktop client resolves sign-in to an identity record that carries no MFA factor of its own, so it routes to enrollment, which then correctly refuses. The web client resolves through to the primary record, which does have the factor. I offer this as a hypothesis rather than a conclusion, because the failure was also observed with the original-domain address plus password before Google SSO was ever used. Identity resolution may therefore be part of the story rather than all of it.
Expected vs actual
- Expected: an account with a verified SMS factor receives an MFA challenge against that factor.
- Actual: the desktop client routes to phone enrollment, enrollment correctly refuses, and there is no path forward.
Impact
Signing in with a ChatGPT account is impossible from the desktop app. The only workaround is API-key auth, which:
- bills the Platform organization per token on top of an already-paid Business seat,
- disables cloud tasks, and
- disables remote control from mobile, because that feature requires account authentication rather than an API key.
Other users on Windows with the same workspace are not affected, which is further evidence this is specific to the macOS desktop client path.
Related
- #25828 (phone verification cannot send a code)
- #32657 (desktop verification uses an unknown phone number)
- #30892 (CLI login blocked, unknown foreign number)
- #27872 (local auth redirects to add-phone while Cloud/API work, the same shape as this report)
An OpenAI support case is open on this with a full network capture and a screen recording. I am happy to share the case number, account identifiers, and the capture privately with anyone triaging.
4 Comments
Update: the client contains no phone-enrollment logic, so this is server-side.
I inspected the installed macOS app directly. Across both
Contents/Resources/app.asar(197 MB Electron bundle) andContents/Resources/codex(265 MB Rust binary):The desktop client cannot be choosing to route to phone enrollment, because no such route or logic is compiled into it. It authenticates as
app_EMoamEEZ73f0CkXaXp7hrannand follows wherever the hosted flow at auth.openai.com sends it. The web client uses a different client id and is not redirected.Conclusion: auth.openai.com is applying a different phone-verification policy per OAuth client id. This is consistent with the two observations a client-side explanation cannot account for: an auto-update from
26.715.31925to26.715.72359changed nothing, and the identical Google identity plus passkey succeeds in the browser minutes apart.Practical implication: no app release will fix this. The fix is a server-side policy change scoped to that client id. This likely belongs with whoever owns the auth service rather than the desktop app team.
Reproducible on any stock install:
Root cause captured: the server issues
add_phoneafter authentication succeeds, keyed on the destination appI captured the failing transaction at the network level using CDP response-body capture: two controlled Codex logins in an isolated, freshly created Chrome profile with no cookies, plus a web control in that same profile.
The response that causes it
POST https://auth.openai.com/api/accounts/mfa/verifyreturns HTTP 200, meaning authentication succeeded. Its body:(
mfa_challenge_factorsabbreviated to itsfactor_typevalues; account identifiers removed.)What this establishes
mfa/verifyreturns 200 and the passkey is accepted.add_phoneis an additional post-authentication gate, not a fallback for failed MFA.continue_urlandpage.typeare both server-issued. The client cannot be choosing this:add-phone,add_phone,issue_challenge,phone_verification, andrequiresPhoneall return 0 occurrences acrossapp.asarand the bundledcodexbinary.add_phoneoutcome. Any theory based on identity resolution, SSO linkage, or a renamed domain is ruled out.app_name_enum: oaicli,destination_app_name: Codex.The contradiction
That same response lists
smsamong the account's registered MFA factors while simultaneously directing the user to add a phone number. The account already has a verified phone factor, and the server knows it.Entering that already-verified number at
/add-phonereturns "already linked to the maximum number of accounts" (the documented 3-verifications-per-number reuse cap). So the enrollment being demanded is impossible to complete. That is the deadlock: a gate demands an enrollment that a separate policy forbids.Control: the web client, same browser profile, minutes later
chatgpt.comsigns in normally with no phone gate. The web client performs no OAuth authorize at all (first-party/api/auth/signin/openai) and requests no scopes. That same session then successfully calls/backend-api/aip/connectors/list_accessible, so the account already holds working connector access with zero phone verification.The likely trigger
The Codex authorize request:
Codex requests connector API scopes as part of login; the web client requests none. If granting those scopes pulls in a phone-verification requirement, that accounts for every observation here, including why the same account already has connector access through a surface that never asks.
Not a desktop-app bug
This reproduces identically via
codex loginin the CLI and via the merged ChatGPT/Codex macOS app. Different binaries, same failure. No client release can fix it.What would fix it
A server-side change: do not apply the
add_phonerequirement to sessions whose destination app is Codex when the account already has registered MFA factors, matching the web client's behavior.Unrelated gotcha that cost me hours
Each
codex loginretry kills the previouslocalhost:1455callback listener. Any browser tab from an earlier attempt therefore hangs forever after you submit credentials, because nothing is listening to receive the callback. Only the tab opened by the currently running login can complete. If you are stuck on an endless spinner after entering your password, that is why.Related open issues with the same shape: #25828, #32657, #30892, #27872.
Same issue here on the current ChatGPT macOS desktop app. Web login works normally, but clicking Log in from the desktop app opens Chrome and redirects directly to auth.openai.com/add-phone. Phone verification then says my number is associated with the maximum number of accounts. Fresh reinstall and clearing local app preferences did not help. macOS app version 1.2026.184. Bundle ID appears to be
same same same same...
codex login, .codex removal, codex login --device-auth
all of these leads to same thing.. Anyone, solutions? looks like it would be easier to buy credits rather than creating new account....