From fdbce2080c6681c57ecd66aad001d6504251650d Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 27 Sep 2026 19:06:36 +0000 Subject: [PATCH] Fix Unix socket connections through long symlink paths (#48772) ## Why An advertised control socket path can exceed the Unix socket path limit even when its symlink target is short enough to connect to. ## What changed On Unix, resolve the socket path and retry the connection when the initial attempt returns `InvalidInput`. ## Testing Add a regression test that creates control sockets under two long home paths, checks that their symlink targets are distinct, and verifies that clients can connect through both advertised paths. GitOrigin-RevId: 2541f0a9b02d85035fa89fe26bbcd36d5cd0cc1e --- .../src/transport/unix_socket_tests.rs | 42 +++++++++++++++++++ codex-rs/uds/src/lib.rs | 9 +++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server-transport/src/transport/unix_socket_tests.rs b/codex-rs/app-server-transport/src/transport/unix_socket_tests.rs index 2452d1fc9a..e19834f8fa 100644 --- a/codex-rs/app-server-transport/src/transport/unix_socket_tests.rs +++ b/codex-rs/app-server-transport/src/transport/unix_socket_tests.rs @@ -55,6 +55,48 @@ fn listen_unix_socket_accepts_relative_custom_path() { ); } +#[cfg(unix)] +#[tokio::test] +async fn long_control_socket_paths_connect_to_distinct_daemons() { + let temp_dir = tempfile::TempDir::new().expect("temp dir"); + let long_parent = temp_dir.path().join("x".repeat(120)); + let mut sockets = Vec::new(); + let mut acceptors = Vec::new(); + + for name in ["first", "second"] { + let codex_home = long_parent.join(name); + std::fs::create_dir_all(&codex_home).expect("codex home"); + let socket_path = app_server_control_socket_path(&codex_home).expect("socket path"); + let (transport_event_tx, _transport_event_rx) = + mpsc::channel::(CHANNEL_CAPACITY); + let shutdown_token = CancellationToken::new(); + let acceptor = start_control_socket_acceptor( + socket_path.clone(), + transport_event_tx, + shutdown_token.clone(), + DaemonShutdownAccess::Disabled, + ) + .await + .expect("control socket acceptor should start"); + sockets.push(socket_path); + acceptors.push((shutdown_token, acceptor)); + } + + assert_ne!( + std::fs::read_link(sockets[0].as_path()).expect("first socket target"), + std::fs::read_link(sockets[1].as_path()).expect("second socket target") + ); + for socket in &sockets { + connect_to_socket(socket.as_path()) + .await + .expect("client should connect through long path"); + } + for (shutdown_token, acceptor) in acceptors { + shutdown_token.cancel(); + acceptor.await.expect("acceptor should stop"); + } +} + #[tokio::test] async fn control_socket_acceptor_upgrades_and_forwards_websocket_text_messages_and_pings() { let temp_dir = tempfile::TempDir::new().expect("temp dir"); diff --git a/codex-rs/uds/src/lib.rs b/codex-rs/uds/src/lib.rs index cecd09ddef..5fecf770be 100644 --- a/codex-rs/uds/src/lib.rs +++ b/codex-rs/uds/src/lib.rs @@ -164,7 +164,14 @@ mod platform { } pub(super) async fn connect_stream(socket_path: &Path) -> IoResult { - UnixStream::connect(socket_path).await + match UnixStream::connect(socket_path).await { + Err(err) if err.kind() == ErrorKind::InvalidInput => { + // The advertised path may exceed sun_path even when its symlink + // target is a short socket path. Resolve it before retrying. + UnixStream::connect(fs::canonicalize(socket_path).await?).await + } + result => result, + } } pub(super) async fn is_stale_socket_path(socket_path: &Path) -> IoResult {