codex - 💡(How to fix) Fix Codex mobile pairing can get stuck when local global state keeps revoked client/environment IDs

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Codex Desktop mobile pairing can get stuck in a stale state after a failed or revoked mobile enrollment. In my case, logging out and back in on the phone, restarting both apps, and enabling MFA did not recover the connection.

The desktop setup page stayed on "Approve on mobile device" with a QR code, while the iPhone stayed on the "Open Codex" / security setup flow and never completed pairing.

The underlying problem appears to be that Codex Desktop kept local remote-control enrollment state in ~/.codex/.codex-global-state.json even after the corresponding cloud-side client/environment records had been removed or no longer existed.

Root Cause

The current failure mode is hard for users to recover from because all obvious steps look successful but do not actually reset the broken state:

  • mobile logout/login
  • desktop restart
  • mobile reinstall/restart
  • MFA enablement

The UI gives the impression that the user still needs to approve on the phone, but the phone and desktop are effectively waiting on incompatible/stale pairing state.

Fix Action

Workaround

The workaround was to clear the local remote/mobile global-state keys after fully quitting Codex Desktop.

Important: do not edit this file while Codex is still running, because Codex may write the old in-memory state back on quit.

# 1. Quit Codex Desktop completely first, e.g. Cmd+Q.
# 2. Confirm the main app process is gone.
ps -axo pid,args | grep '/Applications/Codex.app/Contents/MacOS/Codex'

# 3. Back up and remove only the local mobile/remote pairing keys.
STATE="$HOME/.codex/.codex-global-state.json"
cp "$STATE" "$STATE.mobile-pairing-backup.$(date +%Y%m%d%H%M%S)"

node <<'NODE'
const fs = require("fs");
const path = require("path");

const file = path.join(process.env.HOME, ".codex", ".codex-global-state.json");
const state = JSON.parse(fs.readFileSync(file, "utf8"));

for (const key of [
  "electron-remote-control-client-enrollments",
  "electron-local-remote-control-environment-id",
  "electron-local-remote-control-installation-id",
  "codex-mobile-setup-completed",
]) {
  delete state[key];
}

const atomState = state["electron-persisted-atom-state"];
if (atomState && typeof atomState === "object" && !Array.isArray(atomState)) {
  for (const key of Object.keys(atomState)) {
    const lower = key.toLowerCase();
    if (
      lower.includes("remote-control") ||
      lower.includes("remote_control") ||
      lower.includes("codex-mobile")
    ) {
      delete atomState[key];
    }
  }
}

fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
NODE

Then reopen Codex Desktop, start the Codex mobile setup again, and scan a fresh QR code from the iPhone.

Code Example

~/.codex/.codex-global-state.json

---

electron-remote-control-client-enrollments
electron-local-remote-control-environment-id
electron-local-remote-control-installation-id

---

# 1. Quit Codex Desktop completely first, e.g. Cmd+Q.
# 2. Confirm the main app process is gone.
ps -axo pid,args | grep '/Applications/Codex.app/Contents/MacOS/Codex'

# 3. Back up and remove only the local mobile/remote pairing keys.
STATE="$HOME/.codex/.codex-global-state.json"
cp "$STATE" "$STATE.mobile-pairing-backup.$(date +%Y%m%d%H%M%S)"

node <<'NODE'
const fs = require("fs");
const path = require("path");

const file = path.join(process.env.HOME, ".codex", ".codex-global-state.json");
const state = JSON.parse(fs.readFileSync(file, "utf8"));

for (const key of [
  "electron-remote-control-client-enrollments",
  "electron-local-remote-control-environment-id",
  "electron-local-remote-control-installation-id",
  "codex-mobile-setup-completed",
]) {
  delete state[key];
}

const atomState = state["electron-persisted-atom-state"];
if (atomState && typeof atomState === "object" && !Array.isArray(atomState)) {
  for (const key of Object.keys(atomState)) {
    const lower = key.toLowerCase();
    if (
      lower.includes("remote-control") ||
      lower.includes("remote_control") ||
      lower.includes("codex-mobile")
    ) {
      delete atomState[key];
    }
  }
}

fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
NODE
RAW_BUFFERClick to expand / collapse

Summary

Codex Desktop mobile pairing can get stuck in a stale state after a failed or revoked mobile enrollment. In my case, logging out and back in on the phone, restarting both apps, and enabling MFA did not recover the connection.

The desktop setup page stayed on "Approve on mobile device" with a QR code, while the iPhone stayed on the "Open Codex" / security setup flow and never completed pairing.

The underlying problem appears to be that Codex Desktop kept local remote-control enrollment state in ~/.codex/.codex-global-state.json even after the corresponding cloud-side client/environment records had been removed or no longer existed.

Environment

  • Codex Desktop: 26.513.31313
  • Local app server observed during debugging: 0.131.0-alpha.9
  • ChatGPT iOS app: 1.2026.125
  • Host OS: macOS
  • Phone: iPhone / iOS
  • MFA: enabled before the final retry

Symptoms

  1. Desktop shows the Codex mobile setup QR code and waits at "Approve on mobile device".
  2. iPhone ChatGPT/Codex flow remains stuck around "Open Codex" / security setup.
  3. Reinstalling/restarting the mobile app did not fix it.
  4. Logging out and back in on the mobile app did not fix it.
  5. Restarting Codex Desktop did not fix it.
  6. Enabling MFA did not fix it.

Diagnostics

Using the existing Codex Desktop auth token, I inspected the remote-control backend state:

  • GET /backend-api/wham/remote/control/clients?limit=100
  • GET /backend-api/codex/remote/control/environments?limit=100
  • GET /backend-api/wham/remote/control/mfa_requirement

At first, the account had many stale phone/tablet clients in pending_enrollment plus one enrolled desktop client. The account also had a Codex Desktop remote environment.

After deleting the pending mobile clients and the old desktop client/environment, the cloud-side state was clean:

  • clients = 0
  • environments = 0
  • mfa_requirement = required

The access token already reflected MFA (amr included MFA/OTP), so MFA itself did not appear to be the remaining blocker.

However, Codex Desktop still had stale local remote-control state in:

~/.codex/.codex-global-state.json

Specifically, the file still contained keys like:

electron-remote-control-client-enrollments
electron-local-remote-control-environment-id
electron-local-remote-control-installation-id

Those values pointed at the old client/environment IDs that no longer existed server-side.

Workaround

The workaround was to clear the local remote/mobile global-state keys after fully quitting Codex Desktop.

Important: do not edit this file while Codex is still running, because Codex may write the old in-memory state back on quit.

# 1. Quit Codex Desktop completely first, e.g. Cmd+Q.
# 2. Confirm the main app process is gone.
ps -axo pid,args | grep '/Applications/Codex.app/Contents/MacOS/Codex'

# 3. Back up and remove only the local mobile/remote pairing keys.
STATE="$HOME/.codex/.codex-global-state.json"
cp "$STATE" "$STATE.mobile-pairing-backup.$(date +%Y%m%d%H%M%S)"

node <<'NODE'
const fs = require("fs");
const path = require("path");

const file = path.join(process.env.HOME, ".codex", ".codex-global-state.json");
const state = JSON.parse(fs.readFileSync(file, "utf8"));

for (const key of [
  "electron-remote-control-client-enrollments",
  "electron-local-remote-control-environment-id",
  "electron-local-remote-control-installation-id",
  "codex-mobile-setup-completed",
]) {
  delete state[key];
}

const atomState = state["electron-persisted-atom-state"];
if (atomState && typeof atomState === "object" && !Array.isArray(atomState)) {
  for (const key of Object.keys(atomState)) {
    const lower = key.toLowerCase();
    if (
      lower.includes("remote-control") ||
      lower.includes("remote_control") ||
      lower.includes("codex-mobile")
    ) {
      delete atomState[key];
    }
  }
}

fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
NODE

Then reopen Codex Desktop, start the Codex mobile setup again, and scan a fresh QR code from the iPhone.

Expected behavior

Codex Desktop should detect when the locally stored remote-control enrollment references a server-side client/environment that no longer exists, then automatically discard the stale local pairing state and start a fresh enrollment.

At minimum, the mobile setup UI should provide a clear Reset mobile pairing state or Repair mobile setup action that clears:

  • stale pending mobile clients
  • stale local desktop client enrollment
  • stale local environment/installation IDs

Why this matters

The current failure mode is hard for users to recover from because all obvious steps look successful but do not actually reset the broken state:

  • mobile logout/login
  • desktop restart
  • mobile reinstall/restart
  • MFA enablement

The UI gives the impression that the user still needs to approve on the phone, but the phone and desktop are effectively waiting on incompatible/stale pairing state.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Codex Desktop should detect when the locally stored remote-control enrollment references a server-side client/environment that no longer exists, then automatically discard the stale local pairing state and start a fresh enrollment.

At minimum, the mobile setup UI should provide a clear Reset mobile pairing state or Repair mobile setup action that clears:

  • stale pending mobile clients
  • stale local desktop client enrollment
  • stale local environment/installation IDs

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING