openai / openai/codex

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

Open
#45,246 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app app-server bug connectivity performance remote windows-os
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?
  1. On a Windows host with Codex Desktop and Remote Control enabled, accumulate several thousand unarchived threads (e.g. run codex exec in a loop, or any workflow that spawns many subagents; state_5.sqlite threads table with archived=0).
  2. From the ChatGPT mobile app, open Remote and select the host.
  3. Observe the phone sits on connecting, then fails with the generic "couldn't connect to desktop" error. Host logs show initialize + thread/list arriving, then the client connection closed ~30 s later before thread/list returned.
  4. 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/list for 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 exec and 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/list 46.6 s with ~920 threads / 40 GB of rollouts), #22411 (thread/list loads all session files), #24427 (Desktop slow with many threads in one project), #39954 (Windows/Android reconnect loop after initialize/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 to archived_sessions/), run nightly. Pinned/sectioned threads and long-running goals are untouched because the filter uses recency_at_ms, not created_at_ms.
  • The logs_2.sqlite on this host had also grown to 3.4 GB with 91% free pages (~128k live rows); a manual VACUUM brought 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.