ChatGPT iOS Codex SSH connection works on linux, but the bootstrap/proxy flow is fragile and hardcoded
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:
- Open a non-TTY SSH command and run a shell probe that discovers
codexwithcommand -v codex, checkscodex --version, and printsuname -s. - Try to use
codex app-server proxy, or startcodex app-server --listen "unix://"if no app server is available. - 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
ncthat 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 proxyor a configured socket path. This makes non-defaultCODEX_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, runclear, print menus, or emit other interactive output can corrupt the protocol or make the app spin forever. - Noninteractive SSH
PATHmay not include the user-installedcodex, 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:
- Install or use a Linux environment where
ncexists but does not support Unix sockets vianc -U. - Start or allow the iOS SSH connection flow to start a Codex app server on the Linux host.
- Connect from ChatGPT iOS using the Codex SSH connection flow.
- The SSH connection succeeds and the host can run
codex, but the final proxy step fails whennc -U "$control_socket"is executed.
Another reproducible failure mode:
- Configure the SSH user shell startup to launch
byobu/tmux, runclear, or print an interactive menu. - Connect from ChatGPT iOS using the Codex SSH connection flow.
- 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
codexsomewhere visible to noninteractive SSH commands. - Suppress all shell startup output when
SSH_ORIGINAL_COMMANDis set and no TTY is allocated. - Install an
ncimplementation 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 proxyfor the final proxy step, instead of directly requiringnc -Uagainst a hardcoded socket path. - If a fallback is needed, detect support for
nc -Uexplicitly, or support common alternatives such asncat -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.sockin 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, orunexpected 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 /
ProxyJumpstyle 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.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
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 ascannot 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 avoidbash -l -ifor protocol commands, or otherwise tolerate/suppress shell startup and job-control output.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:
if command -v codex ...umask 077; mkdir -p -- ...; nohup codex app-server --listen unix:// ... &codex app-server proxy$SHELLwith-l -i -c, then runs/bin/sh -c "$CODEX_REMOTE_PAYLOAD"On native Windows OpenSSH, this only became usable after setting Git Bash as
DefaultShellandDefaultShellCommandOption=-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-x64binary, 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:
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.
Confirmed this was the root cause for my Debian + Tailscale + ChatGPT iOS setup as well.
The iPhone app could:
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:
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.ws:///wss://endpoints.So the iOS SSH flow could potentially work like this:
codex, check the version, and start or discovercodex app-server --listen ws://127.0.0.1:<remote-port>.ws://127.0.0.1:<local-forwarded-port>; SSH carries that traffic to127.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 loopbackws://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.