Skip to content

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

Open

Description

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.

Activity

  1. added
    iOSIssues related to the Codex iOS app
    app-serverIssues involving app server protocol or interfaces
    connectivityIssues involving networking or endpoint connectivity problems (disconnections)
    on May 17, 2026
  2. 92645417d9e5c763259dbebc306e3e commented on May 17, 2026

    @92645417d9e5c763259dbebc306e3e
    Author

    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.

  3. nielslindor commented on May 17, 2026

    @nielslindor

    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.

  4. LinusAurel commented on May 25, 2026

    @LinusAurel

    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

    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
  5. 92645417d9e5c763259dbebc306e3e commented on May 26, 2026

    @92645417d9e5c763259dbebc306e3e
    Author

    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.

  6. yhvictor commented on Oct 4, 2026

    @yhvictor

    I reproduced another concrete failure caused by the hardcoded iOS SSH bootstrap.

    Environment: NixOS 26.05, OpenSSH 10.5p1, codex-cli 0.160.0, Ed25519 authentication.

    Server-side tracing captured this probe:

    export PATH=/usr/bin:/bin
    uname -s

    NixOS does not provide /usr/bin/uname; uname normally comes from the user or system profile. Because the probe replaces PATH, it exits 127 and iOS quickly shows the host as Offline.

    This workaround immediately fixed the connection:

    systemd.tmpfiles.rules = [
      "L+ /usr/bin/uname - - - - /run/current-system/sw/bin/uname"
    ];

    Afterward, iOS successfully started codex app-server proxy.

    The probe should preserve the remote login PATH instead of assuming FHS paths, and the UI should expose the failed command and exit status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app-serverIssues involving app server protocol or interfacesbugSomething isn't workingconnectivityIssues involving networking or endpoint connectivity problems (disconnections)iOSIssues related to the Codex iOS appremote

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions