From 75cf6d01ac7f864389b14a9253f4bffaec6becdb Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 25 Jun 2026 13:47:49 +0100 Subject: [PATCH] Fix long Windows working-directory launches --- .../core/src/unified_exec/process_manager.rs | 4 +- .../src/command_resolution.rs | 2 +- .../src/command_resolution_tests.rs | 23 --------- codex-rs/windows-sandbox-rs/src/conpty/mod.rs | 3 +- .../src/elevated/runner_client.rs | 3 +- codex-rs/windows-sandbox-rs/src/env_tests.rs | 19 ------- codex-rs/windows-sandbox-rs/src/process.rs | 3 +- .../src/unified_exec/backends/legacy.rs | 2 +- codex-rs/windows-sandbox-rs/src/winutil.rs | 49 +++++++++++++++++++ 9 files changed, 59 insertions(+), 49 deletions(-) diff --git a/codex-rs/core/src/unified_exec/process_manager.rs b/codex-rs/core/src/unified_exec/process_manager.rs index 3cbb34f682..d0883ed5ab 100644 --- a/codex-rs/core/src/unified_exec/process_manager.rs +++ b/codex-rs/core/src/unified_exec/process_manager.rs @@ -1050,7 +1050,7 @@ impl UnifiedExecProcessManager { path: request.cwd.clone(), })?; - let (program, args) = request + let (_program, args) = request .command .split_first() .ok_or(UnifiedExecError::MissingCommandLine)?; @@ -1069,7 +1069,7 @@ impl UnifiedExecProcessManager { )) })?; #[cfg(not(target_os = "windows"))] - let program = program.as_str(); + let program = _program.as_str(); let spawn_result = if tty { codex_utils_pty::pty::spawn_process_with_inherited_fds( program, diff --git a/codex-rs/windows-sandbox-rs/src/command_resolution.rs b/codex-rs/windows-sandbox-rs/src/command_resolution.rs index 9c6ec53024..29cdd7dd33 100644 --- a/codex-rs/windows-sandbox-rs/src/command_resolution.rs +++ b/codex-rs/windows-sandbox-rs/src/command_resolution.rs @@ -107,7 +107,7 @@ fn windows_search_dirs(cwd: &Path, env_map: &HashMap) -> Vec { diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs index d9a644441e..ea543ff752 100644 --- a/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs +++ b/codex-rs/windows-sandbox-rs/src/unified_exec/backends/legacy.rs @@ -380,7 +380,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy( let process_handle = Arc::new(StdMutex::new(Some(pi.hProcess))); let wait_handle = Arc::clone(&process_handle); - let command_for_wait = launch.command.clone(); + let command_for_wait = launch.command; let hpc_for_wait = hpc_handle.clone(); std::thread::spawn(move || { let _desktop = desktop; diff --git a/codex-rs/windows-sandbox-rs/src/winutil.rs b/codex-rs/windows-sandbox-rs/src/winutil.rs index 45378b4bb5..71ee4ea662 100644 --- a/codex-rs/windows-sandbox-rs/src/winutil.rs +++ b/codex-rs/windows-sandbox-rs/src/winutil.rs @@ -1,6 +1,7 @@ use anyhow::Result; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; +use std::path::Path; use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HLOCAL; @@ -22,6 +23,32 @@ pub fn to_wide>(s: S) -> Vec { v } +const MAX_PATH_UTF16_UNITS: usize = 260; +const EXTENDED_PATH_PREFIX: &[u16] = &[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]; +const DEVICE_PATH_PREFIX: &[u16] = &[b'\\' as u16, b'\\' as u16, b'.' as u16, b'\\' as u16]; + +/// Encodes a CreateProcess path, adding an extended-length prefix only when needed. +pub(crate) fn to_win32_path_wide(path: &Path) -> Vec { + let wide = path.as_os_str().encode_wide().collect::>(); + if !path.is_absolute() + || wide.len() < MAX_PATH_UTF16_UNITS + || wide.starts_with(EXTENDED_PATH_PREFIX) + || wide.starts_with(DEVICE_PATH_PREFIX) + { + return wide.into_iter().chain([0]).collect(); + } + + let mut extended = EXTENDED_PATH_PREFIX.to_vec(); + if wide.starts_with(&[b'\\' as u16, b'\\' as u16]) { + extended.extend("UNC\\".encode_utf16()); + extended.extend_from_slice(&wide[2..]); + } else { + extended.extend(wide); + } + extended.push(0); + extended +} + /// Quote a single Windows command-line argument following the rules used by /// CommandLineToArgvW/CRT so that spaces, quotes, and backslashes are preserved. /// Reference behavior matches Rust std::process::Command on Windows. @@ -208,7 +235,10 @@ fn sid_bytes_from_string(sid_str: &str) -> Result> { #[cfg(test)] mod tests { use super::argv_to_command_line; + use super::to_wide; + use super::to_win32_path_wide; use pretty_assertions::assert_eq; + use std::path::Path; #[test] fn argv_to_command_line_quotes_each_argument_independently() { @@ -238,4 +268,23 @@ mod tests { "pwsh.exe -Command \"Write-Output \\\"hello world\\\"\"" ); } + + #[test] + fn create_process_paths_add_extended_prefixes_only_for_long_paths() { + let long_tail = ["long-working-directory-segment"; 10].join(r"\"); + let long_drive = format!(r"C:\{long_tail}"); + let long_unc = format!(r"\\localhost\C$\{long_tail}"); + + assert_eq!( + to_win32_path_wide(Path::new(&long_drive)), + to_wide(format!(r"\\?\{long_drive}")) + ); + assert_eq!( + to_win32_path_wide(Path::new(&long_unc)), + to_wide(format!(r"\\?\UNC\{}", &long_unc[2..])) + ); + for path in [r"C:\short", r"\\localhost\C$\short", r"\\?\C:\already"] { + assert_eq!(to_win32_path_wide(Path::new(path)), to_wide(path)); + } + } }