From 2eecc1a2e4eb380ca9373c186c2b35945b3ecb17 Mon Sep 17 00:00:00 2001 From: Jakob Malmo Date: Fri, 7 Nov 2025 23:49:17 +0100 Subject: [PATCH] fix(wsl): normalize Windows paths during update (#6086) (#6097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running under WSL, the update command could receive Windows-style absolute paths (e.g., `C:\...`) and pass them to Linux processes unchanged, which fails because WSL expects those paths in `/mnt//...` form. This patch adds a tiny helper in the CLI (`cli/src/wsl_paths.rs`) that: - Detects WSL (`WSL_DISTRO_NAME` or `"microsoft"` in `/proc/version`) - Converts `X:\...` → `/mnt/x/...` `run_update_action` now normalizes the package-manager command and arguments under WSL before spawning. Non-WSL platforms are unaffected. Includes small unit tests for the converter. **Fixes:** #6086, #6084 Co-authored-by: Eric Traut --- codex-rs/cli/src/main.rs | 8 +++- codex-rs/cli/src/wsl_paths.rs | 76 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 codex-rs/cli/src/wsl_paths.rs diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 929fd9e7..d69eb5a1 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -26,8 +26,10 @@ use std::path::PathBuf; use supports_color::Stream; mod mcp_cmd; +mod wsl_paths; use crate::mcp_cmd::McpCli; +use crate::wsl_paths::normalize_for_wsl; use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::features::is_known_feature_key; @@ -270,7 +272,11 @@ fn run_update_action(action: UpdateAction) -> anyhow::Result<()> { let (cmd, args) = action.command_args(); let cmd_str = action.command_str(); println!("Updating Codex via `{cmd_str}`..."); - let status = std::process::Command::new(cmd).args(args).status()?; + let command_path = normalize_for_wsl(cmd); + let normalized_args: Vec = args.iter().map(normalize_for_wsl).collect(); + let status = std::process::Command::new(&command_path) + .args(&normalized_args) + .status()?; if !status.success() { anyhow::bail!("`{cmd_str}` failed with status {status}"); } diff --git a/codex-rs/cli/src/wsl_paths.rs b/codex-rs/cli/src/wsl_paths.rs new file mode 100644 index 00000000..56ce8668 --- /dev/null +++ b/codex-rs/cli/src/wsl_paths.rs @@ -0,0 +1,76 @@ +use std::ffi::OsStr; + +/// WSL-specific path helpers used by the updater logic. +/// +/// See https://github.com/openai/codex/issues/6086. +pub fn is_wsl() -> bool { + #[cfg(target_os = "linux")] + { + if std::env::var_os("WSL_DISTRO_NAME").is_some() { + return true; + } + match std::fs::read_to_string("/proc/version") { + Ok(version) => version.to_lowercase().contains("microsoft"), + Err(_) => false, + } + } + #[cfg(not(target_os = "linux"))] + { + false + } +} + +/// Convert a Windows absolute path (`C:\foo\bar` or `C:/foo/bar`) to a WSL mount path (`/mnt/c/foo/bar`). +/// Returns `None` if the input does not look like a Windows drive path. +pub fn win_path_to_wsl(path: &str) -> Option { + let bytes = path.as_bytes(); + if bytes.len() < 3 + || bytes[1] != b':' + || !(bytes[2] == b'\\' || bytes[2] == b'/') + || !bytes[0].is_ascii_alphabetic() + { + return None; + } + let drive = (bytes[0] as char).to_ascii_lowercase(); + let tail = path[3..].replace('\\', "/"); + if tail.is_empty() { + return Some(format!("/mnt/{drive}")); + } + Some(format!("/mnt/{drive}/{tail}")) +} + +/// If under WSL and given a Windows-style path, return the equivalent `/mnt//…` path. +/// Otherwise returns the input unchanged. +pub fn normalize_for_wsl>(path: P) -> String { + let value = path.as_ref().to_string_lossy().to_string(); + if !is_wsl() { + return value; + } + if let Some(mapped) = win_path_to_wsl(&value) { + return mapped; + } + value +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn win_to_wsl_basic() { + assert_eq!( + win_path_to_wsl(r"C:\Temp\codex.zip").as_deref(), + Some("/mnt/c/Temp/codex.zip") + ); + assert_eq!( + win_path_to_wsl("D:/Work/codex.tgz").as_deref(), + Some("/mnt/d/Work/codex.tgz") + ); + assert!(win_path_to_wsl("/home/user/codex").is_none()); + } + + #[test] + fn normalize_is_noop_on_unix_paths() { + assert_eq!(normalize_for_wsl("/home/u/x"), "/home/u/x"); + } +}