app-server UDS closes standard WebSocket-over-UDS handshake

Resolved 💬 2 comments Opened Apr 26, 2026 by kcosr Closed Apr 26, 2026

Summary

codex app-server --listen unix://... starts and creates a Unix socket in codex-cli 0.125.0, but a standard WebSocket-over-UDS client is closed during the HTTP upgrade with socket hang up.

Repro

Start app-server:

codex app-server --listen unix:///home/kevin/codex.sock

The socket exists and is owned by Codex:

srw-------. 1 kevin kevin 0 Apr 26 11:52 /home/kevin/codex.sock
ss: users:(("codex",pid=1096289,fd=20)) LISTEN /home/kevin/codex.sock

Connect with a Node ws client over that UDS path:

import { createConnection } from "node:net";
import { WebSocket } from "ws";

const ws = new WebSocket("ws://localhost/rpc", {
  createConnection: () => createConnection({ path: "/home/kevin/codex.sock" }),
  perMessageDeflate: false,
});

ws.on("open", () => console.log("open"));
ws.on("error", (err) => console.log("error", err.message));
ws.on("close", (code, reason) => console.log("close", code, reason.toString()));

Observed:

error socket hang up
close 1006

The same error appears when using task-runner's Codex UDS transport:

TASK_RUNNER_CODEX_UDS_PATH=~/codex.sock node apps/cli/dist/cli.js run --agent planner "Testing, respond OK"
# -- attempt 1 --
# socket hang up

Troubleshooting notes

I reproduced this outside task-runner with a direct Node ws probe, so this is not specific to task-runner JSON-RPC handling. I also reproduced against a fresh temporary app-server socket.

With tracing enabled on the temporary app-server:

RUST_LOG=codex_app_server=trace,tokio_tungstenite=trace \
  CODEX_HOME=/tmp/codex-home \
  codex app-server --listen unix:///tmp/codex-probe.sock

Codex logged:

INFO codex_app_server::transport::unix_socket: app-server control socket listening socket_path=/tmp/.../codex-probe.sock
WARN codex_app_server::transport::websocket: websocket receive error: WebSocket protocol error: Encountered invalid opcode: 7

That looks like the server is treating the HTTP upgrade request byte G from GET /rpc HTTP/1.1 as an already-established websocket frame, rather than completing the websocket HTTP Upgrade handshake.

Expected

The UDS app-server transport should accept a standard websocket HTTP Upgrade handshake over the Unix socket, matching the app-server README description of unix://PATH and the existing client_async("ws://localhost/rpc", stream) style test coverage.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗