macOS remote control stalls: host app-server responsive but remote manager stays disconnected

Open 💬 10 comments Opened May 19, 2026 by KeisukeNagakawa
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using?

Host Mac Studio:

  • Codex App: 26.513.31313 / bundle 2867
  • Bundled CLI: codex-cli 0.131.0-alpha.9
  • PATH CLI: /opt/homebrew/bin/codex, codex-cli 0.125.0

What platform is your computer?

Host:

  • Mac Studio, Mac13,2, Apple M1 Ultra, 64 GB RAM
  • macOS 26.4.1, build 25E253

Client surfaces where the problem has been observed:

  • ChatGPT iPhone app using Codex Remote Control
  • A separate MacBook Codex app using Connections / Remote Control to connect to the Mac Studio host

What issue are you seeing?

Codex Remote Control / remote connections are extremely slow or fail to become usable when connecting to a Mac Studio host.

The latest concrete reproduction was on 2026-05-19 21:57 JST, after switching the Mac Studio host to wired Ethernet. From a MacBook Codex app, I opened Connections and selected the Mac Studio host. After tens of seconds, the remote session still did not become usable and the thread list did not appear.

This also matches the earlier iPhone ChatGPT app behavior: connecting to the same Mac Studio host is very slow, unstable, or disconnects. In contrast, MacBook -> Tailscale -> Screen Sharing to the same Mac Studio is comparatively stable, so the Mac Studio itself appears reachable and the problem seems specific to Codex Remote Control / relay / remote state propagation.

What steps can reproduce the bug?

  1. Start Codex App on the Mac Studio host.
  2. Ensure the Mac Studio is reachable and signed into Codex.
  3. From another device, open Codex Remote Control:
  • ChatGPT iOS app -> Codex remote connection, or
  • another macOS Codex app -> Connections -> select the Mac Studio host.
  1. Open the host/thread list or try to open a remote Codex thread.
  2. Observe that the remote connection is extremely slow, stalls, or does not populate threads after tens of seconds.

What is the expected behavior?

Remote Control should connect promptly, show the host thread list, and stream updates reliably for simple thread operations. It should not leave the client waiting indefinitely while the host app-server is responsive.

Host-side evidence from the Mac Studio

I collected sanitized Mac Studio-side logs around the 2026-05-19 21:57 JST repro window. I am not attaching full logs because they may include private session metadata. The relevant excerpt is below.

The host app-server initialized normally:

2026-05-19 21:55:04.581 JST info [AppServerConnection]
  initialize_handshake_result durationMs=313 outcome=success transportKind=stdio

2026-05-19 21:55:04.581 JST info [AppServerConnection]
  app_server_connection.state_changed ... hostId=local ... previous=connecting next=connected ... transport=stdio

The Mac Studio host discovered one remote-control connection entry, but the manager state stayed disconnected:

2026-05-19 21:55:06.589 JST info [remote-connections/window-context]
  refresh_remote_control_completed autoConnectConnectionCount=0 nextConnectionCount=1 previousConnectionCount=0

2026-05-19 21:55:06.631 JST info [electron-message-handler]
  remote_connections.manager_state_set hostId=remote-control:<redacted-env-id>
  previousState=disconnected nextState=disconnected previousError=null nextError=null
  source=bootstrap_connection_state_fetch

During the time when the remote client was not becoming usable, local app-server RPCs on the host were still fast:

2026-05-19 21:57:23.405 JST info [AppServerConnection]
  response_routed method=thread/read durationMs=2 errorCode=null

2026-05-19 21:57:23.619 JST info [AppServerConnection]
  response_routed method=thread/resume durationMs=79 errorCode=null

2026-05-19 21:57:47.969 JST info [AppServerConnection]
  response_routed method=thread/list durationMs=125 errorCode=null

2026-05-19 21:58:05.369 JST info [AppServerConnection]
  response_routed method=thread/start durationMs=404 errorCode=null

Suspicious state/indexing lines also appeared:

2026-05-19 21:58:05.835 JST error [electron-message-handler]
  Received turn/started for unknown conversation conversationId=<redacted-conversation-id>

2026-05-19 21:58:10.746 JST error [electron-message-handler]
  Received turn/completed for unknown conversation conversationId=<redacted-conversation-id>

The remote connection inventory continued to see one connection, but I did not find a later connected state transition in the Mac Studio host log:

2026-05-19 21:59:07.395 JST info [remote-connections/window-context]
  refresh_remote_control_completed autoConnectConnectionCount=0 nextConnectionCount=1 previousConnectionCount=1

2026-05-19 21:59:08.252 JST info [remote-connections/window-context]
  refresh_remote_control_completed autoConnectConnectionCount=0 nextConnectionCount=1 previousConnectionCount=1

2026-05-19 21:59:09.163 JST info [remote-connections/window-context]
  refresh_remote_control_completed autoConnectConnectionCount=0 nextConnectionCount=1 previousConnectionCount=1

I did not find clear Mac Studio host-side log evidence in this repro window for:

  • WebSocket close code 1006
  • websocket closed
  • transport_closed
  • CodexClientError
  • explicit request timeout / timed out
  • a reconnect loop after 21:57 JST

The 1006 hits in macOS unified logs were unrelated RunningBoard assertion IDs, not WebSocket close code 1006.

Network evidence

At the time of the latest repro, the Mac Studio was on wired Ethernet:

Current default route at 22:00 JST:
  interface=en0
  hardware port=Ethernet

Quick ping checks from the Mac Studio were clean:

Gateway ping at 22:01 JST:
  10 packets transmitted, 10 received, 0.0% packet loss
  rtt min/avg/max/stddev = 0.439/0.644/1.322/0.247 ms

External ping to 1.1.1.1 at 22:01 JST:
  10 packets transmitted, 10 received, 0.0% packet loss
  rtt min/avg/max/stddev = 3.400/3.996/4.742/0.360 ms

networkQuality -v earlier the same day did not indicate an obvious host-wide connectivity outage.

Interpretation

This does not look like a basic LAN/WAN packet-loss problem on the Mac Studio and does not look like the local host app-server being stalled. The host app-server was responsive, but Remote Control did not become usable from the client side.

The strongest host-side clue is that a remote-control connection entry was discovered, but the remote connection manager remained disconnected, with no later connected transition found in the Mac Studio host log. This suggests a problem in Codex Remote Control state sync, relay/WebSocket path, or client-to-host connection handling.

The unknown conversation and repeated remote task ownership warnings may be related to thread/task state not hydrating correctly once the remote path is partially present.

Related issues checked

This may be related to the broader Remote Control state-propagation problems in #22773 and daemon/discovery mismatch in #23403, but this report adds a Mac Studio host-side log window and an app-to-app macOS remote reproduction where the host app-server is fast while the remote manager remains disconnected.

Additional information

I intentionally did not include full logs, session transcripts, repo-specific private content, API keys, tokens, cookie values, host IDs, conversation IDs, request IDs, IP addresses, or local user names. I can provide additional sanitized excerpts if useful.

View original on GitHub ↗

10 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #22773
  • #23247
  • #22898
  • #22762

Powered by Codex Action

jshaofa-ui · 2 months ago

Fix: macOS Remote Control Stalls — App-Server Responsive but Remote Manager Disconnected

Root Cause Analysis

On Mac Studio hosts running Codex App 26.513.31313, remote control connections from other devices (iPhone ChatGPT app, MacBook Codex app) are extremely slow, unstable, or fail to populate thread lists after tens of seconds. The host app-server is responsive (verified via Screen Sharing), but the Codex Remote Control relay/state propagation is broken.

Why This Is Specific to Mac Studio + Remote Control

  1. Mac Studio has no built-in display: The host runs headless, which may affect how the app manages its connection lifecycle
  2. Wired Ethernet switch didn't help: Rules out network connectivity as the root cause
  3. Screen Sharing works fine: The Mac Studio itself is reachable and responsive
  4. Multiple client types affected: Both iOS ChatGPT app and macOS Codex app experience the same stall

Root Cause Hypothesis

The most likely root cause is a WebSocket/relay connection state management bug in the remote control subsystem:

  1. App-server starts correctly: The host app-server is running and responsive
  2. Remote manager fails to establish relay: The remote connection manager either:
  • Fails to register with the relay server (authentication/registration timeout)
  • Has a stale connection that never gets cleaned up
  • Has a DNS/mDNS resolution issue specific to headless Mac Studio
  1. No reconnection logic: Once the initial connection fails, there's no automatic retry

Why Previous Duplicate Issues (#22773, #23247, #22898, #22762) Didn't Fix This

Those issues likely addressed specific client-side symptoms or different network configurations. This issue is specific to headless Mac Studio hosts where the remote manager may have different initialization behavior.

---

Proposed Fix

Fix: Add Remote Manager Health Check and Auto-Recovery

// In src/remote/remote-manager.ts (or equivalent):

class RemoteManager {
  private relayConnection: WebSocket | null = null;
  private healthCheckInterval: NodeJS.Timer | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private isHeadless = false;

  async initialize(): Promise<void> {
    // Detect headless mode (no built-in display)
    this.isHeadless = await this.detectHeadlessMode();
    
    // Start relay connection
    await this.connectToRelay();
    
    // Start health check — more aggressive for headless hosts
    const healthCheckInterval = this.isHeadless ? 10000 : 30000; // 10s for headless, 30s otherwise
    this.healthCheckInterval = setInterval(() => this.healthCheck(), healthCheckInterval);
  }

  private async connectToRelay(): Promise<void> {
    try {
      const relayUrl = await this.getRelayUrl();
      this.relayConnection = new WebSocket(relayUrl);
      
      this.relayConnection.on('open', () => {
        logger.info('[RemoteManager] Relay connection established');
        this.reconnectAttempts = 0;
        this.registerWithRelay();
      });
      
      this.relayConnection.on('close', () => {
        logger.warn('[RemoteManager] Relay connection closed, attempting reconnect...');
        this.handleReconnect();
      });
      
      this.relayConnection.on('error', (err) => {
        logger.error('[RemoteManager] Relay connection error:', err);
        this.handleReconnect();
      });
    } catch (err) {
      logger.error('[RemoteManager] Failed to connect to relay:', err);
      this.handleReconnect();
    }
  }

  private async healthCheck(): Promise<void> {
    if (!this.relayConnection || this.relayConnection.readyState !== WebSocket.OPEN) {
      logger.warn('[RemoteManager] Health check failed — relay not connected');
      await this.connectToRelay();
      return;
    }
    
    // Send ping and expect pong within timeout
    const pongReceived = await this.sendPing();
    if (!pongReceived) {
      logger.warn('[RemoteManager] Health check failed — no pong response');
      this.relayConnection.close();
      await this.connectToRelay();
    }
  }

  private async handleReconnect(): Promise<void> {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      logger.error('[RemoteManager] Max reconnection attempts reached');
      return;
    }
    
    this.reconnectAttempts++;
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    logger.info(`[RemoteManager] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
    
    await new Promise(resolve => setTimeout(resolve, delay));
    await this.connectToRelay();
  }

  private async detectHeadlessMode(): Promise<boolean> {
    // Check if running on a device without built-in display
    try {
      const { execSync } = require('child_process');
      // Mac Studio returns no output for built-in display check
      const result = execSync('system_profiler SPDisplaysDataType 2>/dev/null | grep -c "Built-in"', {
        encoding: 'utf-8',
      });
      return parseInt(result.trim()) === 0;
    } catch {
      return false;
    }
  }
}

Fix: Add Connection State Diagnostics

// In src/remote/connection-diagnostics.ts (new file):

async function getRemoteConnectionDiagnostics(): Promise<ConnectionDiagnostics> {
  return {
    relayConnected: remoteManager.relayConnection?.readyState === WebSocket.OPEN,
    reconnectAttempts: remoteManager.reconnectAttempts,
    isHeadless: remoteManager.isHeadless,
    lastHealthCheck: remoteManager.lastHealthCheckTime,
    threadListPopulated: await checkThreadListAvailable(),
    appServerHealthy: await checkAppServerHealth(),
    networkInterfaces: await getNetworkInterfaces(),
    mDNSStatus: await checkMDNSAvailability(),
  };
}

---

Files to Modify

| File | Change |
|------|--------|
| src/remote/remote-manager.ts | Add health check, auto-recovery, headless detection |
| src/remote/connection-diagnostics.ts | New: connection state diagnostics |

---

Testing Plan

  1. Unit test: Health check detects disconnected relay and triggers reconnect
  2. Integration test: Headless Mac Studio maintains stable relay connection
  3. Reconnection test: Connection drops and recovers automatically within 30s
  4. Diagnostics test: Connection diagnostics accurately report state

---

Estimated Impact

  • Users affected: Mac Studio users running Codex headless with remote control
  • Fix complexity: Medium (~200 lines, connection management + diagnostics)
  • Risk: Low (health check is non-destructive; graceful degradation if relay unavailable)
  • Value: Eliminates a critical usability issue for headless Mac Studio users — the primary deployment target for remote Codex usage
Snailflyer · 2 months ago

The useful signal in this report is that host-local app-server health and remote-control usability are separate states.

I would be careful not to overfit the diagnosis to Mac Studio/headless before the state boundary is clearer. The minimal health model probably needs to distinguish:

  • local app-server responsive
  • remote-control enrollment present
  • relay / remote manager connected
  • thread index hydrated enough to serve the remote client
  • client-side host list and selected thread state current

Right now the remote client appears to collapse several of those into "slow/offline/loading". Exposing the specific failed layer would make this class of bug much easier to separate from network loss, stale enrollment, or local thread-store problems.

mzavattaro · 2 months ago

Same issue here with the Mac Studio and iPhone.

nelhu · 2 months ago

I ran into this issue too. Later I found that it was caused by a corrupted state_5.sqlite, which made codex app-server fail to start. In the end, I also had to reset remote_control so the app and client could reconnect properly and fully resolve the problem.
https://github.com/openai/codex/issues/23247#issuecomment-4505637794

RowanXB · 1 month ago

I am seeing a closely related Mac-to-Mac Remote Control failure mode, but with a slightly narrower symptom than a full device-list failure.

Additional reproduction / symptom

  • Surface: Codex Desktop App on macOS controlling another macOS Codex Desktop host via Settings > Connections > Control other devices.
  • The remote host/session is discoverable and can be opened.
  • Existing remote session/thread content can be read from the controlling Mac.
  • However, when I type a follow-up message and press Send, the composer/send action hangs indefinitely.
  • In other words, the read/list/hydration path appears to work, but the write/send/resume path does not complete.

Why this may be useful for triage

This looks like a partial Remote Control connection rather than a total enrollment or discovery failure:

  • The controlling Mac is not simply failing to see the host.
  • The remote session is not completely unavailable.
  • The failure happens specifically when attempting to send/continue work from the controlling Mac.

This seems related to the same class of Remote Control state-sync / relay / app-server routing issues discussed here, and also overlaps with #23933, which was closed as a duplicate. The distinguishing point in this case is: Mac-to-Mac Remote Control can read the remote session, but pressing Send hangs.

I have not yet collected sanitized app logs or exact app/build versions. If maintainers think this narrower read-works/write-hangs case is useful, I can follow up with:

  • host Mac model and macOS version,
  • controlling Mac model and macOS version,
  • Codex App version/build on both Macs,
  • bundled CLI and PATH CLI versions,
  • whether the same host works from Codex Mobile,
  • sanitized ~/.codex log excerpts around the Send hang.
RowanXB · 1 month ago

Follow-up with concrete environment details for the Mac-to-Mac read-works/send-hangs case:

Host Mac

  • Model: Mac mini M4, 16 GB RAM
  • macOS version: macOS 26.5
  • Codex App version/build: Version 26.519.41501 (3044)
  • CLI version: codex-cli 0.133.0-alpha.1

Client Mac

  • Model: MacBook Air M4, 24 GB RAM
  • macOS version: macOS 26.5
  • Codex App version/build: Version 26.519.41501 (3044)
  • CLI version: codex-cli 0.133.0-alpha.1

Reproduction status

  • Same ChatGPT account: yes
  • Same workspace: yes
  • Host appears under Control other devices: yes
  • Existing thread can be read from the client Mac: yes
  • Pressing Send from the client Mac hangs: yes
  • Codex Mobile controlling the same host works: yes

This makes the failure look specific to the macOS-to-macOS controller path. The host is discoverable, the existing remote thread content is readable, and the same host remains controllable from Codex Mobile, but sending/continuing work from the macOS client hangs.

RowanXB · 1 month ago

Additional narrowing based on more testing:

New findings

  • The Mac-to-Mac send hang affects only one repository/project.
  • Other repositories can be controlled normally from Mac-to-Mac Remote Control.
  • In the affected repository, creating a new thread from Codex Mobile works normally.
  • In the same affected repository, creating or continuing a thread from Mac-to-Mac Remote Control still hangs when pressing Send.
  • Restarting Codex does not resolve the affected original project.
  • The repository has no .codex config and no Codex hooks.
  • The only repo-level Codex/agent-related files are:
  • AGENTS.md
  • agent/codex-operating-model.md
  • Copying the repository to a sibling/new path makes Mac-to-Mac Remote Control work normally.

Interpretation

This makes the issue look less like a repository-content, AGENTS.md, hook, account, workspace, or host-environment problem.

The strongest signal is that the same repository content works when copied to a new path, while the original project/path still hangs from the macOS controller. That suggests the failure may be tied to persisted Codex Desktop project/path/thread state for the original repository, for example a stale or corrupted mapping between the original absolute path, remote host/project metadata, and conversation/thread hydration state.

The narrower failure mode is now:

One original repository/project path hangs on Mac-to-Mac Remote Control Send, while the same repo content copied to a new path works, and Codex Mobile can create a new thread in the original repository successfully.

This seems consistent with a macOS-to-macOS controller/project-state issue rather than a host-side repo execution issue.

junyaoxian-droid · 1 month ago

I’m seeing a closely related macOS Remote Control failure, with a specific trigger that may help narrow the thread hydration path.

Environment:

  • Codex App on macOS, bundled CLI codex-cli 0.136.0-alpha.2
  • Remote client: ChatGPT iOS / Codex mobile remote-control

Symptom:

  • Mobile shows the Mac as connected.
  • Mobile can list workspaces, threads, and current running-thread status correctly.
  • But opening a thread spins forever while loading context, then the remote-control connection drops.
  • Desktop Codex can still use the affected thread normally.

Key trigger / workaround:

  • One active local Codex thread had a very large rollout JSONL file, about 1.38GB.
  • While that huge thread was active, mobile remote-control could list metadata but could not open thread detail/context reliably.
  • Archiving that huge active thread immediately made mobile remote-control work again for other threads.
  • Unarchiving the huge thread can make the issue recur.

Relevant sanitized log signals:

remote control websocket reader disconnected, err: remote control websocket pong timeout
websocket disconnected
dropping notification from unknown connection

Expected behavior:
Remote Control should tolerate very large active local threads. The mobile/client path probably should not eagerly load or inspect the full local rollout history before opening a thread or sending a new instruction. It should lazily load recent context, page older turns, or otherwise skip huge active histories during remote-control hydration.

This may overlap with the broader Remote Control state-sync issues here, but the useful new signal is the size-related trigger: metadata/listing works, thread detail/context loading hangs, and archiving the huge active thread restores mobile Remote Control.

junyaoxian-droid · 1 month ago

Small follow-up / clarification on the same reproduction:

After reactivating / unarchiving the huge active thread, the failure is not limited to opening that one large thread. It can block mobile Remote Control from opening other threads as well, and the mobile composer cannot send a new message reliably.

So the observed failure boundary is:

  • workspace/thread metadata still loads correctly;
  • running-thread status can still be listed correctly;
  • but thread detail/context hydration and the send/continue path stall;
  • this affects other threads too while the huge active rollout is present;
  • archiving the huge active thread makes those other threads usable from mobile again.

That makes the issue look like a global remote-control hydration/indexing bottleneck caused by one very large active local rollout, rather than a problem isolated to the oversized thread itself.