app-server WS handshake fails with iOS URLSessionWebSocketTask (-1005) before initialize
Summary
When connecting to codex app-server from an iOS client (URLSessionWebSocketTask), TCP connection reaches the server, but the WebSocket session drops immediately on iOS with NSURLErrorDomain Code=-1005 before JSON-RPC initialize can start.
Environment
- Codex CLI:
codex-cli 0.104.0 - Server start command:
``bash``
codex app-server --listen ws://0.0.0.0:8080
- Listener check:
``bash``
lsof -nP -iTCP:8080 -sTCP:LISTEN
# codex-aar ... TCP *:8080 (LISTEN)
- Client: iOS app (Swift +
URLSessionWebSocketTask) - Network: same Tailscale tailnet (closed/private network)
Reproduction
- Start server:
``bash``
codex app-server --listen ws://0.0.0.0:8080
- Connect from iOS app via
URLSessionWebSocketTaskto either:
ws://{host}:{port}
- Attempt to connect and begin app-server session.
Expected
WebSocket handshake succeeds and app-server can proceed to JSON-RPC initialize.
Actual
iOS immediately reports connection lost (-1005) and app-server session cannot initialize.
iOS logs
Task <09A1000B-A247-490A-8542-EFBC39F47539>.<4> HTTP load failed, 696/0 bytes (error code: -1005 [4:-4])
Task <09A1000B-A247-490A-8542-EFBC39F47539>.<4> finished with error [-1005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSErrorFailingURLStringKey=http://100.103.155.65:8080/, NSErrorFailingURLKey=http://100.103.155.65:8080/, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalWebSocketTask <09A1000B-A247-490A-8542-EFBC39F47539>.<4>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalWebSocketTask <09A1000B-A247-490A-8542-EFBC39F47539>.<4>, NSLocalizedDescription=The network connection was lost.}
Task <CFFE1E8F-BF3E-4E35-B2C5-57508D91EFA0>.<5> HTTP load failed, 696/0 bytes (error code: -1005 [4:-4])
Task <CFFE1E8F-BF3E-4E35-B2C5-57508D91EFA0>.<5> finished with error [-1005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSErrorFailingURLStringKey=http://100.103.155.65:8080/, NSErrorFailingURLKey=http://100.103.155.65:8080/, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalWebSocketTask <CFFE1E8F-BF3E-4E35-B2C5-57508D91EFA0>.<5>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalWebSocketTask <CFFE1E8F-BF3E-4E35-B2C5-57508D91EFA0>.<5>, NSLocalizedDescription=The network connection was lost.}
app-server logs
codex app-server (WebSockets)
listening on: ws://0.0.0.0:8080
note: this is a raw WS server; consider running behind TLS/auth for real remote use
websocket client connected from 100.103.155.65:64251
websocket client connected from 100.103.155.65:64254
(Repeated attempts show many websocket client connected ... lines, but iOS side still fails before usable session.)
Notes / suspicion
I suspect a handshake compatibility problem around Sec-WebSocket-Extensions (e.g. permessage-deflate) from iOS URLSessionWebSocketTask, which cannot easily disable extension negotiation.
Could app-server either:
- Tolerate/ignore unsupported extension negotiation from clients like iOS URLSession, or
- Offer a flag/config to disable extension negotiation requirements for compatibility?
Happy to test a branch/build if needed.
5 Comments
@shuto-S Thanks for closing this issue. i'm still hitting what looks like the same handshake problem from React Native (Expo), and wanted to ask how this was resolved on your side.
Environment:
0.104.0codex app-server --listen ws://192.168.11.229:9000Current behavior:
initializefailed to complete websocket handshake: WebSocket protocol error: Missing, duplicated or incorrect header sec-websocket-extensionsImportant detail:
wsclient withperMessageDeflate: falsecan connect and complete:initialize -> initialized -> thread/list.Could you clarify what fix/workaround was used when closing this?
Sec-WebSocket-Extensionsfrom iOS/RN clients?stdio-to-ws, custom WS bridge, etc.)?Thanks!
@cl1107 Thanks for the question — and sorry, I forgot to include the solution earlier.
I run Codex app-server behind a small WebSocket proxy:
[ws_strip_extensions_proxy.js](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html) (header-normalizing proxy)
run_app_server_stack.sh (starts/monitors app-server + proxy together)
This setup avoids connecting React directly to codex app-server and normalizes the handshake request before forwarding upstream.
What it covers:
In short, this mainly mitigates handshake failures caused by extension-header incompatibility (e.g., permessage-deflate negotiation mismatch).
It does not by itself solve unrelated issues such as Origin policy, auth headers, or TLS configuration.
If you know a better way, I’d love to hear it.
ws_strip_extensions_proxy.js
run_app_server_stack.sh
I closed this thread because I was able to solve it personally, but I'm reopening it because there seems to be demand for it.
I'd be happy if the codex could solve it on its own.
Thanks a lot for sharing this — this is very helpful.
Before your reply, codex had implemented a simpler WS relay version:
wsserver that accepts client connectionsws://...connection tocodex app-serverperMessageDeflate: falseon the upstream sideclient <-> upstream)That version works for quick validation, but it does not explicitly normalize handshake headers at the raw HTTP level.
I think Your version is better
hope codex can solve it on its own
Hey! I ran into a similar pattern in our bug knowledge base and thought this might help.
What's happening: codex app-server's WebSocket upgrade response includes a Sec-WebSocket-Extensions: permessage-deflate header that iOS URLSessionWebSocketTask cannot negotiate properly — iOS interprets the mismatch as a broken connection and fires -1005 before any data frame is exchanged
What worked for us:
Run a thin WebSocket proxy (Node.js ws or http-proxy) between iOS and codex app-server that intercepts the HTTP 101 upgrade response and strips the Sec-WebSocket-Extensions header. Alternatively, if you control the server config, disable permessage-deflate on the server side. For React Native (Expo), the same fix applies since Expo's WebSocket also delegates to URLSessionWebSocketTask on iOS.
Hope this helps! Let me know if it doesn't match your case — happy to dig deeper. 🦞
---
<sub>🦞 Confucius Debug — community knowledge base for AI agent bugs. Free to search via MCP.</sub>