From bd3dd2f9f948d4e121eadd0c114d2fc1ba196ede Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 6 Jul 2026 20:45:56 -0700 Subject: [PATCH 1/2] tui: prefer CODEX_HOME for IDE IPC (#22966) --- codex-rs/tui/src/ide_context/ipc.rs | 183 +++++++++++++++++++++++++--- 1 file changed, 167 insertions(+), 16 deletions(-) diff --git a/codex-rs/tui/src/ide_context/ipc.rs b/codex-rs/tui/src/ide_context/ipc.rs index 57942d931037..2d81c3542f52 100644 --- a/codex-rs/tui/src/ide_context/ipc.rs +++ b/codex-rs/tui/src/ide_context/ipc.rs @@ -138,7 +138,21 @@ type IdeContextStream = UnixDeadlineStream; #[cfg(windows)] type IdeContextStream = super::windows_pipe::WindowsPipeStream; -#[cfg(any(unix, windows))] +#[cfg(unix)] +pub(crate) fn fetch_ide_context(workspace_root: &Path) -> Result { + let primary_socket_path = codex_utils_home_dir::find_codex_home() + .map(|codex_home| primary_ipc_socket_path(codex_home.as_path())); + let uid = unsafe { libc::getuid() }; + let legacy_socket_path = legacy_ipc_socket_path(&std::env::temp_dir(), uid); + fetch_ide_context_from_unix_socket_paths( + primary_socket_path, + legacy_socket_path, + workspace_root, + IDE_CONTEXT_REQUEST_TIMEOUT, + ) +} + +#[cfg(windows)] pub(crate) fn fetch_ide_context(workspace_root: &Path) -> Result { fetch_ide_context_from_socket( default_ipc_socket_path(), @@ -153,11 +167,13 @@ pub(crate) fn fetch_ide_context(_workspace_root: &Path) -> Result PathBuf { - let uid = unsafe { libc::getuid() }; - std::env::temp_dir() - .join("codex-ipc") - .join(format!("ipc-{uid}.sock")) +fn primary_ipc_socket_path(codex_home: &Path) -> PathBuf { + codex_home.join("ipc").join("ipc.sock") +} + +#[cfg(unix)] +fn legacy_ipc_socket_path(temp_dir: &Path, uid: libc::uid_t) -> PathBuf { + temp_dir.join("codex-ipc").join(format!("ipc-{uid}.sock")) } #[cfg(windows)] @@ -170,7 +186,7 @@ fn default_ipc_socket_path() -> PathBuf { PathBuf::new() } -#[cfg(any(unix, windows))] +#[cfg(windows)] fn fetch_ide_context_from_socket( socket_path: PathBuf, workspace_root: &Path, @@ -182,11 +198,18 @@ fn fetch_ide_context_from_socket( } #[cfg(unix)] -fn connect_stream( - socket_path: PathBuf, - deadline: Instant, -) -> Result { - UnixDeadlineStream::connect(socket_path, deadline).map_err(IdeContextError::Connect) +fn fetch_ide_context_from_unix_socket_paths( + primary_socket_path: std::io::Result, + legacy_socket_path: PathBuf, + workspace_root: &Path, + timeout: Duration, +) -> Result { + let deadline = Instant::now() + timeout; + let mut stream = primary_socket_path + .and_then(|socket_path| UnixDeadlineStream::connect(socket_path, deadline)) + .or_else(|_| UnixDeadlineStream::connect(legacy_socket_path, deadline)) + .map_err(IdeContextError::Connect)?; + fetch_ide_context_from_stream(&mut stream, workspace_root, deadline) } #[cfg(unix)] @@ -861,6 +884,134 @@ mod tests { } } + fn spawn_ide_context_server( + listener: std::os::unix::net::UnixListener, + active_selection_content: &'static str, + ) -> std::thread::JoinHandle<()> { + std::thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept"); + let request = read_frame(&mut stream, test_deadline()).expect("read ide-context"); + let request_id = request + .get("requestId") + .and_then(Value::as_str) + .expect("ide-context request id"); + write_ide_context_response(&mut stream, request_id, active_selection_content); + }) + } + + fn fetch_test_ide_context( + primary_socket_path: PathBuf, + legacy_socket_path: PathBuf, + ) -> Result { + fetch_ide_context_from_unix_socket_paths( + Ok(primary_socket_path), + legacy_socket_path, + Path::new("/repo"), + Duration::from_secs(1), + ) + } + + fn assert_listener_unused(listener: &std::os::unix::net::UnixListener) { + listener + .set_nonblocking(true) + .expect("set listener nonblocking"); + assert_eq!( + listener + .accept() + .expect_err("listener should not receive a connection") + .kind(), + std::io::ErrorKind::WouldBlock + ); + } + + #[test] + fn unix_ipc_socket_paths_use_codex_home_temp_dir_and_uid() { + let codex_home = Path::new("/home/test/.codex"); + let temp_dir = Path::new("/custom/tmp"); + + assert_eq!( + primary_ipc_socket_path(codex_home), + codex_home.join("ipc").join("ipc.sock") + ); + assert_eq!( + legacy_ipc_socket_path(temp_dir, /*uid*/ 1234), + temp_dir.join("codex-ipc").join("ipc-1234.sock") + ); + } + + #[test] + fn fetch_ide_context_prefers_primary_socket() { + use std::os::unix::net::UnixListener; + + let tempdir = tempfile::tempdir().expect("tempdir"); + let primary_socket_path = tempdir.path().join("primary.sock"); + let legacy_socket_path = tempdir.path().join("legacy.sock"); + let primary_listener = UnixListener::bind(&primary_socket_path).expect("bind primary"); + let legacy_listener = UnixListener::bind(&legacy_socket_path).expect("bind legacy"); + let server = spawn_ide_context_server(primary_listener, "primary"); + + let context = fetch_test_ide_context(primary_socket_path, legacy_socket_path) + .expect("fetch IDE context from primary socket"); + + server.join().expect("server joins"); + assert_eq!( + context + .active_file + .expect("active file") + .active_selection_content, + "primary" + ); + assert_listener_unused(&legacy_listener); + } + + #[test] + fn fetch_ide_context_falls_back_to_legacy_socket() { + use std::os::unix::net::UnixListener; + + let tempdir = tempfile::tempdir().expect("tempdir"); + let primary_socket_path = tempdir.path().join("missing-primary.sock"); + let legacy_socket_path = tempdir.path().join("legacy.sock"); + let legacy_listener = UnixListener::bind(&legacy_socket_path).expect("bind legacy"); + let server = spawn_ide_context_server(legacy_listener, "legacy"); + + let context = fetch_test_ide_context(primary_socket_path, legacy_socket_path) + .expect("fetch IDE context from legacy socket"); + + server.join().expect("server joins"); + assert_eq!( + context + .active_file + .expect("active file") + .active_selection_content, + "legacy" + ); + } + + #[test] + fn fetch_ide_context_does_not_fall_back_after_primary_protocol_error() { + use std::os::unix::net::UnixListener; + use std::thread; + + let tempdir = tempfile::tempdir().expect("tempdir"); + let primary_socket_path = tempdir.path().join("primary.sock"); + let legacy_socket_path = tempdir.path().join("legacy.sock"); + let primary_listener = UnixListener::bind(&primary_socket_path).expect("bind primary"); + let legacy_listener = UnixListener::bind(&legacy_socket_path).expect("bind legacy"); + let server = thread::spawn(move || { + let (mut stream, _) = primary_listener.accept().expect("accept primary"); + read_frame(&mut stream, test_deadline()).expect("read ide-context"); + write_frame(&mut stream, &json!({ "type": "unexpected" })) + .expect("write invalid response"); + }); + + let err = fetch_test_ide_context(primary_socket_path, legacy_socket_path) + .expect_err("invalid primary response should fail"); + + server.join().expect("server joins"); + assert!(matches!(err, IdeContextError::InvalidResponse(_))); + assert_listener_unused(&legacy_listener); + } + #[cfg(unix)] #[test] fn unix_deadline_stream_uses_remaining_deadline_for_blocking_reads() { @@ -994,16 +1145,16 @@ mod tests { }); let context = - fetch_ide_context_from_socket(socket_path, Path::new("/repo"), Duration::from_secs(1)) + fetch_test_ide_context(socket_path, tempdir.path().join("missing-legacy.sock")) .expect("fetch ide context"); server.join().expect("server joins"); assert_eq!( context .active_file - .as_ref() - .map(|file| file.active_selection_content.as_str()), - Some("use") + .expect("active file") + .active_selection_content, + "use" ); } } From 105faa426ebda9ea20b7cc236749877822e5b091 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 6 Jul 2026 21:00:51 -0700 Subject: [PATCH 2/2] codex: fix CI failure on PR #31347 --- codex-rs/tui/src/ide_context/ipc.rs | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/codex-rs/tui/src/ide_context/ipc.rs b/codex-rs/tui/src/ide_context/ipc.rs index 2d81c3542f52..bcccd7e9fdfa 100644 --- a/codex-rs/tui/src/ide_context/ipc.rs +++ b/codex-rs/tui/src/ide_context/ipc.rs @@ -889,12 +889,16 @@ mod tests { active_selection_content: &'static str, ) -> std::thread::JoinHandle<()> { std::thread::spawn(move || { - let (mut stream, _) = listener.accept().expect("accept"); - let request = read_frame(&mut stream, test_deadline()).expect("read ide-context"); - let request_id = request - .get("requestId") - .and_then(Value::as_str) - .expect("ide-context request id"); + let Ok((mut stream, _)) = listener.accept() else { + panic!("accept failed"); + }; + let request = match read_frame(&mut stream, test_deadline()) { + Ok(request) => request, + Err(err) => panic!("read ide-context failed: {err}"), + }; + let Some(request_id) = request.get("requestId").and_then(Value::as_str) else { + panic!("ide-context request did not include a request id"); + }; write_ide_context_response(&mut stream, request_id, active_selection_content); }) } @@ -912,16 +916,13 @@ mod tests { } fn assert_listener_unused(listener: &std::os::unix::net::UnixListener) { - listener - .set_nonblocking(true) - .expect("set listener nonblocking"); - assert_eq!( - listener - .accept() - .expect_err("listener should not receive a connection") - .kind(), - std::io::ErrorKind::WouldBlock - ); + if let Err(err) = listener.set_nonblocking(true) { + panic!("set listener nonblocking failed: {err}"); + } + match listener.accept() { + Err(err) => assert_eq!(err.kind(), std::io::ErrorKind::WouldBlock), + Ok(_) => panic!("listener should not receive a connection"), + } } #[test]