ChatGPT iOS Codex SSH connection works on linux, but the bootstrap/proxy flow is fragile and hardcoded

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

What version of Codex CLI is running?

0.131.0-alpha.22, (iOS 1.2026.125)

What subscription do you have?

Plus

Which model were you using?

_No response_

What platform is your computer?

Linux 6.12.65-gentoo x86_64 AMD Ryzen 9 5950X 16-Core Processor

What terminal emulator and version are you using (if applicable)?

byobu

What issue are you seeing?

This is not a generic "cannot connect" report. I was able to get the ChatGPT iOS SSH-based Codex connection working against a Linux host, but only after working around several assumptions in the SSH bootstrap/proxy flow. The current flow appears usable, but fragile.

From server-side SSH logging, the iOS client appears to:

  1. Open a non-TTY SSH command and run a shell probe that discovers codex with command -v codex, checks codex --version, and prints uname -s.
  2. Try to use codex app-server proxy, or start codex app-server --listen "unix://" if no app server is available.
  3. For the final proxy step, connect directly to:
control_socket="${CODEX_HOME:-$HOME/.codex}/app-server-control/app-server-control.sock"
exec nc -U "$control_socket"

The connection can fail or hang for reasons that are hard to diagnose from the iOS UI:

  • Some Linux distributions ship a traditional nc that does not support -U. In that case the iOS app quickly reports a lost network connection, even though SSH and Codex are otherwise working.
  • The final proxy step hardcodes the default Unix socket path instead of using codex app-server proxy or a configured socket path. This makes non-default CODEX_HOME, low-privilege relay users, containers, jump hosts, and custom socket locations difficult.
  • The bootstrap probe uses an interactive/login shell path. User shell startup files that launch byobu/tmux, run clear, print menus, or emit other interactive output can corrupt the protocol or make the app spin forever.
  • Noninteractive SSH PATH may not include the user-installed codex, even when normal terminal SSH sessions work.
  • iOS currently reports broad messages such as waiting for Codex app / network connection lost, without exposing which bootstrap step failed.

What steps can reproduce the bug?

One reproducible failure mode on Linux:

  1. Install or use a Linux environment where nc exists but does not support Unix sockets via nc -U.
  2. Start or allow the iOS SSH connection flow to start a Codex app server on the Linux host.
  3. Connect from ChatGPT iOS using the Codex SSH connection flow.
  4. The SSH connection succeeds and the host can run codex, but the final proxy step fails when nc -U "$control_socket" is executed.

Another reproducible failure mode:

  1. Configure the SSH user shell startup to launch byobu/tmux, run clear, or print an interactive menu.
  2. Connect from ChatGPT iOS using the Codex SSH connection flow.
  3. The app may hang or fail because the command protocol receives unrelated shell output before the expected Codex probe/proxy output.

Workarounds that made the Linux connection usable:

  • Put codex somewhere visible to noninteractive SSH commands.
  • Suppress all shell startup output when SSH_ORIGINAL_COMMAND is set and no TTY is allocated.
  • Install an nc implementation with Unix socket support, such as OpenBSD netcat.
  • Use the same Unix user that owns the Codex app-server socket, because the default socket directory/file are user-private.

What is the expected behavior?

The SSH connection flow should be robust against common Linux environments and should report actionable bootstrap errors.

Specifically:

  • Prefer codex app-server proxy for the final proxy step, instead of directly requiring nc -U against a hardcoded socket path.
  • If a fallback is needed, detect support for nc -U explicitly, or support common alternatives such as ncat -U / socat.
  • Avoid requiring an interactive shell for command probing, or document/enforce that startup files must not emit output for the non-TTY command path.
  • Respect a configured or discoverable app-server socket path, rather than assuming ${CODEX_HOME:-$HOME/.codex}/app-server-control/app-server-control.sock in the iOS SSH script.
  • Surface the failing stage and stderr in the iOS UI, for example: codex not found, nc does not support -U, socket not found, permission denied, or unexpected shell output.

Additional information

This also affects safer deployment models. The iOS SSH UI currently appears to support username/password, but some users will want safer or more flexible options:

  • SSH private key support, including paste/import or system keychain integration.
  • Jump host / ProxyJump style support.
  • A way to set the command, CODEX_HOME, or app-server socket path explicitly.

The direct cloud remote-control path is separate from this SSH route. The SSH route is useful because it can work over a user's private network/VPN, but the current hardcoded final proxy step makes it hard to run through a restricted relay user or container without matching the default per-user socket path.

  • All of the above was accomplished with the help of the Codex CLI.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 3 months ago

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

  • #22831
  • #22965

Powered by Codex Action

92645417d9e5c763259dbebc306e3e · 3 months ago

Additional detail: the iOS SSH bootstrap appears to invoke commands through bash -l -i -c ... without allocating a TTY. On Linux this can fail even before Codex is reached: interactive/login shell startup files may launch tmux/byobu or print output, and bash itself can emit job-control warnings such as cannot set terminal process group / no job control in this shell.

In my case, the connection only became reliable after forcing the SSH command path to unwrap and execute the inner /bin/sh -c ... command directly, avoiding the interactive bash layer. This suggests the SSH bootstrap should avoid bash -l -i for protocol commands, or otherwise tolerate/suppress shell startup and job-control output.

nielslindor · 3 months ago

This also lines up with what I found in the Desktop app bundle, and with a live repro on native Windows OpenSSH.

The remote flow does not look like a normal SSH shell session. It looks more like an SSH-backed app-server proxy/websocket flow, with POSIX shell assumptions baked into the bootstrap path:

  • probe: if command -v codex ...
  • start server: umask 077; mkdir -p -- ...; nohup codex app-server --listen unix:// ... &
  • proxy: codex app-server proxy
  • wrapper: re-enters $SHELL with -l -i -c, then runs /bin/sh -c "$CODEX_REMOTE_PAYLOAD"

On native Windows OpenSSH, this only became usable after setting Git Bash as DefaultShell and DefaultShellCommandOption=-lc.

That got the app-server/proxy part running, but it failed later during folder validation. The app-server is the native Windows codex-win32-x64 binary, while the bootstrap shell is Git Bash/MSYS, so the flow crosses the MSYS/native Windows path boundary after bootstrap succeeds.

So the same bootstrap/proxy design seems to fail in different ways depending on the platform:

  • Linux/iOS: shell startup output, login/interactive shell behavior, or job-control noise can break the command protocol.
  • Native Windows PowerShell: the POSIX shell syntax fails earlier.
  • Windows with Git Bash/MSYS: bootstrap gets further, but native/MSYS path handling breaks later.

The common issue seems to be that the remote bootstrap assumes a POSIX-like shell environment instead of treating the app-server proxy as a stricter, platform-aware transport boundary.

LinusAurel · 3 months ago

Confirmed this was the root cause for my Debian + Tailscale + ChatGPT iOS setup as well.

The iPhone app could:

  • SSH successfully
  • run codex --version
  • run codex app-server proxy

but would repeatedly reconnect and eventually show “The network connection was lost”.

The actual failure was:

nc -U <socket>

because Debian was using nc.traditional, which does not support -U.

Verification before fix:

nc -U /tmp/test

output:

nc: invalid option -- 'U'

Fix:

sudo apt install netcat-openbsd
sudo update-alternatives --config nc

After switching /bin/nc to nc.openbsd, the ChatGPT iOS remote-control connection started working immediately.

Additional context:

  • macOS Codex App worked even before this fix
  • ChatGPT iOS app failed until nc.openbsd was installed
  • Debian trixie
  • Tailscale SSH host
  • Codex CLI 0.133.0
92645417d9e5c763259dbebc306e3e · 3 months ago

One possible design improvement: keep SSH as the outer transport, but use it to tunnel a loopback WebSocket app-server connection instead of making the SSH command stream itself carry the long-lived app-server proxy protocol.

Looking at the repo, Codex already has a WebSocket app-server transport:

  • codex app-server --listen ... can expose a WebSocket listener.
  • The app-server client path already connects to ws:// / wss:// endpoints.
  • The WebSocket listener banner says loopback listeners should use SSH port forwarding for remote access.

So the iOS SSH flow could potentially work like this:

  1. Open SSH to the remote host.
  2. Use that SSH session for simple bootstrap/discovery only: find codex, check the version, and start or discover codex app-server --listen ws://127.0.0.1:<remote-port>.
  3. Keep the SSH connection open and create a local port forward from the client side to the remote loopback WebSocket listener.
  4. Connect the app client to ws://127.0.0.1:<local-forwarded-port>; SSH carries that traffic to 127.0.0.1:<remote-port> on the remote host.

That would keep the app-server listener bound to remote loopback, avoid exposing a public TCP port, and avoid relying on nc -U, Unix socket paths, or command-stream cleanliness for the long-lived transport. The remote shell would only be used for startup/discovery, so shell startup output would be much less likely to corrupt the actual app-server protocol.

This also seems to fit the existing auth model: auth tokens are already allowed for wss:// or loopback ws:// URLs, and an SSH tunnel would make the client-facing endpoint loopback.

Even if the current Unix-socket proxy path remains supported, an explicit “SSH tunnel + loopback WebSocket” mode would likely be more robust across Linux distributions, containers, jump hosts, and nonstandard shell environments.

Generated by Codex CLI.