Update unix socket transport to use WebSocket upgrade (#19244)

## Summary
- Switch Unix socket app-server connections to perform the standard
WebSocket HTTP Upgrade handshake
- Update the Unix socket test to exercise a real upgrade over the Unix
stream
- Refresh the app-server README to describe the new Unix socket behavior

## Testing
- `cargo test -p codex-app-server transport::unix_socket_tests`
- `just fmt`
- `git diff --check`
This commit is contained in:
willwang-openai
2026-04-24 13:06:51 -07:00
committed by GitHub
Unverified
parent a3cccbd8ed
commit 687c5d9081
3 changed files with 16 additions and 10 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ Supported transports:
- stdio (`--listen stdio://`, default): newline-delimited JSON (JSONL)
- websocket (`--listen ws://IP:PORT`): one JSON-RPC message per websocket text frame (**experimental / unsupported**)
- unix socket (`--listen unix://` or `--listen unix://PATH`): websocket frames over `$CODEX_HOME/app-server-control/app-server-control.sock` or a custom socket path without HTTP upgrade
- unix socket (`--listen unix://` or `--listen unix://PATH`): websocket connections over `$CODEX_HOME/app-server-control/app-server-control.sock` or a custom socket path, using the standard HTTP Upgrade handshake
- off (`--listen off`): do not expose a local transport
When running with `--listen ws://IP:PORT`, the same listener also serves basic HTTP health probes:
@@ -39,7 +39,7 @@ Websocket transport is currently experimental and unsupported. Do not rely on it
The unix socket transport is intended for local app-server control-plane clients. `codex app-server proxy`
opens exactly one raw stream connection to `$CODEX_HOME/app-server-control/app-server-control.sock`
by default, or to `--sock PATH` when provided, and proxies bytes between that socket and stdin/stdout.
The socket uses websocket framing directly over the Unix socket, without an HTTP upgrade handshake.
The proxied stream carries the websocket HTTP Upgrade handshake followed by websocket frames.
Security note:
@@ -11,8 +11,7 @@ use futures::StreamExt;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::tungstenite::protocol::Role;
use tokio_tungstenite::accept_async;
use tokio_util::sync::CancellationToken;
use tracing::error;
use tracing::info;
@@ -76,8 +75,13 @@ async fn run_control_socket_acceptor(
let transport_event_tx = transport_event_tx.clone();
tokio::spawn(async move {
let websocket_stream =
WebSocketStream::from_raw_socket(stream, Role::Server, None).await;
let websocket_stream = match accept_async(stream).await {
Ok(websocket_stream) => websocket_stream,
Err(err) => {
warn!("failed to upgrade control socket websocket connection: {err}");
return;
}
};
let (websocket_writer, websocket_reader) = websocket_stream.split();
run_websocket_connection(websocket_writer, websocket_reader, transport_event_tx).await;
});
@@ -16,10 +16,9 @@ use std::path::Path;
use tokio::sync::mpsc;
use tokio::time::Duration;
use tokio::time::timeout;
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::client_async;
use tokio_tungstenite::tungstenite::Bytes;
use tokio_tungstenite::tungstenite::Message as WebSocketMessage;
use tokio_tungstenite::tungstenite::protocol::Role;
use tokio_util::sync::CancellationToken;
#[test]
@@ -54,7 +53,7 @@ fn listen_unix_socket_accepts_relative_custom_path() {
}
#[tokio::test]
async fn control_socket_acceptor_forwards_websocket_text_messages_and_pings() {
async fn control_socket_acceptor_upgrades_and_forwards_websocket_text_messages_and_pings() {
let temp_dir = tempfile::TempDir::new().expect("temp dir");
let socket_path = test_socket_path(temp_dir.path());
let (transport_event_tx, mut transport_event_rx) =
@@ -71,7 +70,10 @@ async fn control_socket_acceptor_forwards_websocket_text_messages_and_pings() {
let stream = connect_to_socket(socket_path.as_path())
.await
.expect("client should connect");
let mut websocket = WebSocketStream::from_raw_socket(stream, Role::Client, None).await;
let (mut websocket, response) = client_async("ws://localhost/rpc", stream)
.await
.expect("websocket upgrade should complete");
assert_eq!(response.status().as_u16(), 101);
let opened = timeout(Duration::from_secs(1), transport_event_rx.recv())
.await