Repository navigation
remote-control relay connection silently disconnects after 20–55 minutes without auto-reconnect (WSL2) #26786
Description
Environment
┌─────────────────────┬────────────────────────────────────────────────────────────────────┐
│ Item │ Detail │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Codex CLI version │ 0.137.0 │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ app-server version │ 0.137.0 │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ OS │ Ubuntu 20.04 on WSL2 (Windows 11) │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ WSL networking mode │ NAT (default) │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ mobile client │ ChatGPT Android app (Codex remote-control) │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Daemon mode │ codex app-server daemon bootstrap --remote-control │
├─────────────────────┼────────────────────────────────────────────────────────────────────┤
│ TCP keepalive │ net.ipv4.tcp_keepalive_time=60, intvl=10, probes=6 (already tuned) │
└─────────────────────┴────────────────────────────────────────────────────────────────────┘
Steps to reproduce
- Start app-server daemon with remote-control: codex remote-control start
- In ChatGPT Android app, connect to the WSL2 Codex environment → works immediately
- Wait 20–55 minutes without any interaction
- Try to access Codex from ChatGPT app again → cannot connect, environment shows as unavailable
Current behavior
The remote-control relay connection dies silently. After disconnection:
- codex app-server daemon version → reports {"status":"running"} (daemon thinks everything is fine)
- ps aux → app-server process still running (same PID)
- remoteControl/status/read API via Unix socket → NO_RESPONSE (socket accepts connection but sends no
data) - app-server.stderr.log → completely empty in some cases; in other cases shows:
rmcp::transport::worker worker quit with fatal: Transport channel closed,
when Client(HttpRequest("http/request failed: error sending request for url
(https://chatgpt.com/backend-api/wham/apps)")) - The relay connection never auto-recovers until a full codex remote-control stop && codex
remote-control start is performed
Two distinct failure modes observed
Mode A — Fatal error (with logs):
Stderr shows rmcp::transport::worker fatal exit with Transport channel closed when the HTTP request to
chatgpt.com/backend-api/wham/apps fails. The relay worker crashes but the app-server process stays
alive, leaving the Unix socket in a zombie state.
Mode B — Silent disconnect (no logs):
No errors in app-server.stderr.log at all. ss -tnp shows the only TCP connection is 127.0.0.1 →
127.0.0.1:7090 (internal proxy), with zero outbound connections to chatgpt.com. The upstream relay
connection has been dropped without any logging.
Pattern observed over 6 days (login-start.log)
Every single day required a fresh daemon restart to recover — the relay never auto-recovered on its
own:
2026-05-31: timedOut=true → restart → connected
2026-06-01: timedOut=true → restart → connected
2026-06-02: timedOut=true → restart → connected
2026-06-03: timedOut=true → restart → connected
2026-06-04: timedOut=true → restart → connected
2026-06-05: timedOut=true → restart → connected
2026-06-06: timedOut=true → restart → connected (then disconnected again after ~55 min)
Expected behavior
- The relay connection should auto-reconnect when the upstream connection to
chatgpt.com/backend-api/wham/apps is lost - Silent disconnections (Mode B) should produce diagnostic logs so the failure reason is traceable
- The remoteControl/status/read API should reflect the actual connection state rather than returning
no response when the relay subsystem is dead - The daemon health check should detect when the relay worker has exited and restart it
Additional context
- chatgpt.com is reachable from WSL2 (curl gets HTTP 405, ~0.8s latency)
- WSL2 DNS errors (getaddrinfo() failed: -5) appear in dmesg occasionally but do not correlate with all
disconnections — the most recent disconnect happened with no DNS errors present - This is NOT a WSL2 TCP keepalive issue — keepalive is already configured and the failure is at the
application layer (relay worker exits / upstream connection drops silently)
Labels: bug, app, cli
Activity
github-actions commentedon Jun 6, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
wananing commentedon Jun 25, 2026
I looked at this against current
upstream/main(f2f80ef442ff84612004bad064991283eb17cdb5) and I think the first PR, if maintainers want one, should stay narrowly scoped to Remote Control relay transport recovery inside the existing app-server process.I do not want to claim this covers every symptom in this issue yet. In particular, if
remoteControl/status/readtimes out and an unrelated local RPC such asconfig/readorthread/listalso times out at the same time, that points above the relay WebSocket layer, likely in the app-server RPC dispatcher/local transport path, and should be debugged separately.For the transport/liveness side, I have a deterministic local fake-relay regression test that exercises this sequence:
connected.connectedand becomesconnecting.initializemessage on the second generation still reaches the transport.I also added a smaller unit test for the lifecycle edge case where one connection worker stops while the sibling worker is still stuck during generation drain. The important assertion there is that Remote Control publishes a non-connected state before drain completes, so local status does not remain stale as
connectedafter the generation is already known to be dead.The local patch is intentionally limited to
codex-app-server-transportRemote Control code:installation_id;RemoteControlWebsocketstate across reconnects;connecting, cancel only the current WebSocket generation, and rely on the existing reconnect loop to create the next generation.Validation on
f2f80ef442ff84612004bad064991283eb17cdb5plus the local patch:cargo fmt -- --config imports_granularity=Itemcompleted locally; stablerustfmtwarns thatimports_granularityis nightly-only.I also ran the full crate test:
The one failure is
transport::remote_control::tests::remote_control_waits_for_account_id_before_enrolling, timing out in the auth-change wakeup path:That failure is reproducible for me outside the new pong-timeout/connection-generation path, so I am treating it as a separate timing issue rather than evidence against the relay-generation recovery test.
Would a maintainer be willing to invite a focused PR for this transport-scoped approach? If not, the next diagnostic data I would collect for this issue is the failure-time split between
remoteControl/status/readand an unrelated local RPC such asconfig/read, to determine whether the stuck state is confined to Remote Control or affects the app-server dispatcher more broadly.mjzcng commentedon Jul 8, 2026
I do not think this solves the real transport auto-reconnect bug described here, but for day-to-day use I found it helpful to have a tiny wrapper that makes the CLI remote-control app-server easier to restart and keep around as a background process:
https://github.com/mjzcng/codex-cli-mobile-pairing
The main flow is:
It uses the experimental app-server pairing APIs to generate a manual pairing code, then leaves the backing
codex app-serverrunning. For this issue specifically,restartis just a workaround when the mobile side goes stale; the proper fix still needs to be in the relay transport / app-server recovery path you outlined.