Remote Control unusable from mobile when host has many unarchived threads: thread/list scales with thread count and exceeds the mobile client's ~30s timeout
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using (From "About Codex" dialog)?
- Windows Store package:
OpenAI.Codex 26.908.4834.0 - Bundled app-server:
codex-cli 0.154.0-alpha.6.2 - Mobile client: ChatGPT for Android (server logs report
client_name="codex_chatgpt_android_remote",client_version="dev")
What subscription do you have?
ChatGPT Pro
What platform is your computer?
Windows 11 Pro 10.0.26200 x64, NVMe SSD. Native Windows host (no WSL).
What issue are you seeing?
Connecting to the desktop host from the ChatGPT mobile app fails most of the time and only occasionally succeeds. Restarting both apps sometimes "fixes" it, but only by coincidence.
The host-side relay websocket is healthy the whole time (wss://chatgpt.com/backend-api/wham/remote/control/server stays Connected for hours). What fails is the first request the phone sends after initialize: thread/list takes 20-42 seconds on the host, and the mobile client gives up at ~30 s. Whether a given connection attempt succeeds is essentially a coin flip on whether that one call lands under 30 s.
The variable is the number of unarchived threads in state_5.sqlite. I measured thread/list (default params, as sent by the phone) against the same 0.154 app-server at three thread counts on the same machine, same session:
| Unarchived threads | thread/list latency |
Mobile result |
|---|---|---|
| 8,361 | 20-42 s (5 samples from phone requests, median 30 s) | fails most attempts |
| 998 | 3.3-3.5 s | works |
| 183 | 1.7-1.9 s | works |
The only change between rows was bulk-archiving threads (setting archived=1 and moving the rollout to archived_sessions/). No app restart, no config change.
Of the 8,361 threads, ~5,000 had source='exec' and ~2,300 were thread_source='subagent', i.e. produced by codex exec automation and spawned subagents. Anyone running orchestration pipelines will accumulate thousands of these quickly (this profile gains ~150/day), so the host degrades toward "mobile can't connect" within a couple of weeks even though the desktop app itself still feels fine.
Evidence from logs_2.sqlite
Phone request/response timing (same connection, thread/list request id 339 and 341):
15:32:14 message_processor app-server request: thread/list connection_id=ConnectionId(41) request_id=Integer(339)
15:32:43 recorder app_server.request{... rpc.method="thread/list" ... app_server.client_name="codex_chatgpt_android_remote"}: Resuming rollout from ".../rollout-...jsonl"
15:32:44 recorder ...: Resumed rollout with 31155 items
15:32:51 message_processor app-server request: thread/list connection_id=ConnectionId(42) request_id=Integer(341)
15:33:22 recorder ...: Resuming rollout from ...
15:33:23 recorder ...: Resumed rollout with 31155 items
Note that ~29 s elapse before the rollout parse starts, so the cost is in the list/scan over the thread set, not (primarily) in parsing one big rollout.
When the phone retries, the host shows the per-device connection being closed on a 30 s cadence:
17:12:15 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(25)
17:12:45 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(26)
17:12:45 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(27)
17:13:45 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(1)
17:14:15 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(28)
17:14:45 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(30)
17:15:15 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(31)
17:15:45 client_tracker forwarding remote control connection closed transport event connection_id=ConnectionId(32)
Meanwhile the host's own relay socket never dropped:
remote control websocket status changed previous_status=Connecting next_status=Connected
connected to app-server remote control websocket ... subscribe_cursor_present=true
What steps can reproduce the bug?
- On a Windows host with Codex Desktop and Remote Control enabled, accumulate several thousand unarchived threads (e.g. run
codex execin a loop, or any workflow that spawns many subagents;state_5.sqlitethreadstable witharchived=0). - From the ChatGPT mobile app, open Remote and select the host.
- Observe the phone sits on connecting, then fails with the generic "couldn't connect to desktop" error. Host logs show
initialize+thread/listarriving, then the client connection closed ~30 s later beforethread/listreturned. - Archive most threads (any method), retry: connection succeeds immediately.
A quick way to measure without a phone: run codex app-server over stdio, send initialize then thread/list with {} params, and time the response. At 8k unarchived threads it takes tens of seconds on a fast NVMe machine.
What is the expected behavior?
thread/listfor the mobile client should be bounded (index-only, paginated, no rollout scans) regardless of how many threads exist. A phone needs the ~20 most recent, not a scan of 8,000.- The mobile client should surface "host is slow to list threads" rather than "couldn't connect to desktop", since the host is connected; the generic error sends users down the wrong troubleshooting path (re-pairing, restarting, checking sleep/network).
- Threads produced by
codex execand subagent spawns should probably not count toward the interactive list at all, or should auto-archive when their parent finishes. Today they pile up invisibly and there's no bulk-archive affordance in the app.
Additional information
- Related: #43565 (Android, WSL2 host,
thread/list46.6 s with ~920 threads / 40 GB of rollouts), #22411 (thread/listloads all session files), #24427 (Desktop slow with many threads in one project), #39954 (Windows/Android reconnect loop afterinitialize/thread/list). This report isolates the unarchived-thread count as the controlling variable with before/after measurements on a native Windows host. - Workaround that fixed it for me: archive everything with no activity in N days (
archived=1,archived_at, move rollout toarchived_sessions/), run nightly. Pinned/sectioned threads and long-running goals are untouched because the filter usesrecency_at_ms, notcreated_at_ms. - The
logs_2.sqliteon this host had also grown to 3.4 GB with 91% free pages (~128k live rows); a manualVACUUMbrought it to 0.26 GB. Probably not the cause here, but the app never compacts it. - Account IDs, installation/server/environment IDs, thread IDs, paths, and conversation content have been omitted.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the app-server stdio entry point and reproduce initialize followed by thread/list using {} parameters against a large state_5.sqlite threads table. Compare timings and logs in logs_2.sqlite, then trace the list operation to identify the scan cost. Done means thread listing stays within the mobile timeout as unarchived thread counts grow, with regression coverage for the measured case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100