macOS: "Control other devices" never connects behind an HTTP proxy — controller-side WebSocket ignores the system proxy
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- electron, javascript, node.js
- Domain
- desktop-dev, networking
Research direction
Start with the controller transport in app.asar's main-process /.vite/build/src-*.js chunk, where the ws WebSocket is created without an agent, and compare it with the electron.net.fetch calls and the separate socksProxyUrl transport. Reproduce behind a macOS system HTTP proxy, then verify the controller WebSocket uses the resolved proxy and reaches the waiting-for-device state instead of timing out.
Written by the indexing model from the issue text.
Description
What version of the Codex App are you using (From "About Codex" dialog)?
26.721.41059 (bundled Contents/Resources/codex 0.146.0)
What subscription do you have?
Business / Team
What platform is your computer?
macOS 26.5.2 (build 25F84), arm64. Electron 42.3.0 / Chromium 150.0.7871.128 / Node 24.14.0.
What issue are you seeing?
On a network where direct egress to chatgpt.com is unavailable and all traffic must go through a local HTTP proxy configured as the macOS system proxy, every part of the app works except Settings > Connections > Control other devices.
The target device sits permanently on Initializing secure connection, occasionally flashing Opening handshake has timed out before returning to the same state, retrying forever.
What works and what doesn't, on the same machine at the same time:
| Path | Stack | Result |
|---|---|---|
All desktop REST API calls (/wham/..., device list, enroll) |
electron.net.fetch → Chromium |
✅ honours system proxy |
| Renderer / webview page loads | Chromium | ✅ honours system proxy |
Being controlled (wss://chatgpt.com/backend-api/wham/remote/control/server) |
Rust Contents/Resources/codex, reads http_proxy |
✅ connects |
Controlling another device (wss://chatgpt.com/backend-api/codex/remote/control/client) |
ws npm package in the Electron main process |
❌ times out |
The controller-side WebSocket appears to be the only network path in the main process that does not go through Chromium's network stack, and it is given no proxy agent, so it lands on Node's https.request — which honours neither the macOS system proxy nor http_proxy/https_proxy.
Enabling a network-layer TUN/VPN-style interception makes it connect immediately, which is consistent with this diagnosis: only interception below the socket layer can help a connection that never consults any proxy configuration.
What steps can reproduce the bug?
- On macOS, configure an HTTP/HTTPS proxy as the system proxy (
System Settings > Network > Proxies, ornetworksetup -setwebproxy/-setsecurewebproxy). Confirm withscutil --proxy. - Ensure direct outbound TCP 443 to
chatgpt.comis not available (restricted network, or block it with a local firewall) so that only the proxy provides connectivity. - Launch ChatGPT.app. Sign in. Confirm the app is fully functional: chat, task list, and
Settings > Connectionscorrectly lists devices — all of this goes through the proxy. - Enable "Control this Mac" on a second Mac signed into the same account, and confirm the pairing is healthy (e.g. controlling that Mac from the ChatGPT iOS app works).
- From the first Mac,
Settings > Connections > Control other devices→ enable the connection to the second Mac. - It stays on
Initializing secure connectionindefinitely.
What is the expected behavior?
The controller-side remote-control WebSocket should use the same proxy configuration as the rest of the app. Since every other main-process request already resolves proxy settings through Chromium, the WebSocket should too.
Additional information
Evidence that the connection is made directly, bypassing the proxy
lsof -a -i -n -P -p <main process pid> while the UI is stuck shows exactly one socket on the Electron main process, going straight out with no proxy involved:
COMMAND PID FD TYPE ... NAME
ChatGPT 75363 190u IPv4 ... TCP 192.168.x.x:62430->A.B.C.D:443 (SYN_SENT)
Zero connections from that PID to the proxy's loopback port. At the same moment, on the same machine:
codex 75867 30u IPv4 TCP 127.0.0.1:62166->127.0.0.1:<proxy port> (ESTABLISHED)
codex 75867 31u IPv4 TCP 127.0.0.1:62393->127.0.0.1:<proxy port> (ESTABLISHED)
codex 75867 32u IPv4 TCP 127.0.0.1:62337->127.0.0.1:<proxy port> (ESTABLISHED)
— the Rust binary goes through the proxy correctly, and the Chromium network service process likewise has multiple established connections to the proxy port. Polling lsof alongside DNS resolution confirmed the direct SYN_SENT target always equals the current DNS answer for chatgpt.com, i.e. the main process resolves and dials chatgpt.com:443 itself instead of handing the hostname to the proxy.
Where it comes from in the shipped bundle
-
Initializing secure connectionis renderer i18n:settings.remoteConnections.deviceConnections.signedInDeviceInitializingSubtitle, inapp.asar→/webview/assets/remote-connections-settings-*.js. -
Opening handshake has timed outis thewspackage's own message (ws@8.18.3perpackage.json), bundled into the main process atapp.asar→/.vite/build/src-*.js. -
The controller transport's connection state machine is in that same main-process chunk:
this.setConnectionProgress(`initializing`); // UI: "Initializing secure connection" await this.connectAndWaitForOpen(); // never resolves this.setConnectionProgress(`waiting-for-device`); // never reached -
The WebSocket is opened with no
agentand nocreateConnection, so it falls through tohttps.globalAgent:const socket = new WebSocket(websocketUrl, { headers, // x-codex-client-id, x-codex-protocol-version, auth handshakeTimeout, // 10_000 perMessageDeflate: false, });The 10 s
handshakeTimeoutmatches the observed cadence exactly: ~10 s onInitializing secure connection, a briefOpening handshake has timed out, then retry. -
For contrast, the controller-side REST calls in the same file (
/codex/remote/control/client/enroll/start|finish,/refresh/start|finish) go throughelectron.net.fetch, which is why enrollment succeeds and only the WebSocket fails. -
There is a
socksProxyUrloption on a different websocket transport class in the same bundle that would build aSocksProxyAgent, but nothing in the bundle ever assigns it, and it is not on the remote-control controller path.
Why environment variables don't help
NODE_USE_ENV_PROXY=1 with HTTPS_PROXY set does work on stock Node 24.14.0 — verified using the Node binary shipped inside the app bundle:
$ ./Contents/Resources/cua_node/bin/node -e '<https.request to chatgpt.com>'
TIMEOUT 10021ms
$ NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://127.0.0.1:<port> \
./Contents/Resources/cua_node/bin/node -e '<same request>'
status 403 698ms
But launching ChatGPT.app itself with NODE_USE_ENV_PROXY=1 + HTTPS_PROXY changes nothing — the main process still dials out directly (verified via lsof, and the launched process's parent was the shell, so the variables did reach it). So Electron does not appear to wire up Node's env-proxy support.
features.respect_system_proxy and network_proxy exist only in the Rust binary — zero occurrences in app.asar — so they cannot affect this path either.
Suggested fix
Give the controller-side WebSocket an agent derived from the app's existing proxy configuration, e.g. resolve via session.resolveProxy(websocketUrl) (which already reflects the system proxy, PAC, and any --proxy-server) and pass a matching HttpsProxyAgent/SocksProxyAgent as ws's agent option. RFC 6455 §4.1 specifies proxy traversal via CONNECT for wss://, and the proxy handles it fine — codex doctor reports HTTP 101 Switching Protocols for a proxied WebSocket handshake on this same machine. The gap is only that no agent is supplied.
Related issues
- openai/codex#29958 — Windows: WebSocket transport times out with
respect_system_proxybut works withHTTP_PROXY/HTTPS_PROXY. Same class of defect (HTTP honours the system proxy, WebSocket does not) on a different platform and layer. - openai/codex#27231 — macOS: Mac-to-Mac remote control fails in both directions while iPhone → Mac works. Same asymmetry this report explains: the iOS path exercises the Mac's proxy-aware server role, while Mac-to-Mac requires the controller role.
- openai/codex#34955 — macOS: app-side network paths not picking up proxy configuration. Adjacent, but distinct: in this report Chromium does correctly use the system proxy, and only the Node-side WebSocket does not.
- openai/codex#29233 — Windows: remote control only works with global proxy environment variables.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- Avg merge
- 1m
- Merged PRs (30d)
- 1k
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.
More from openai/codex
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug CLI windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
macOS sandbox blocks hw.optional.arm64 sysctl, causing Flutter to misdetect Apple Silicon as x64 Openbug CLI sandbox
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug CLI TUI
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
CLI config enhancement skills
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·