From 04a10182ab984c9715bc9df73c3906b3648dbd8e Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Wed, 29 Apr 2026 10:02:51 -0700 Subject: [PATCH] fix windows shell resolution for PowerShell --- codex-rs/core/src/shell.rs | 43 ++++++++++++++++++++++++++++++++ codex-rs/core/src/shell_tests.rs | 11 ++++++++ 2 files changed, 54 insertions(+) diff --git a/codex-rs/core/src/shell.rs b/codex-rs/core/src/shell.rs index 48c760d667..48bd51ac95 100644 --- a/codex-rs/core/src/shell.rs +++ b/codex-rs/core/src/shell.rs @@ -1,5 +1,9 @@ use crate::shell_detect::detect_shell_type; use crate::shell_snapshot::ShellSnapshot; +#[cfg(windows)] +use codex_shell_command::powershell::try_find_powershell_executable_blocking; +#[cfg(windows)] +use codex_shell_command::powershell::try_find_pwsh_executable_blocking; use serde::Deserialize; use serde::Serialize; use std::path::PathBuf; @@ -250,6 +254,10 @@ const POWERSHELL_FALLBACK_PATHS: &[&str] = const POWERSHELL_FALLBACK_PATHS: &[&str] = &[]; fn get_powershell_shell(path: Option<&PathBuf>) -> Option { + #[cfg(windows)] + let shell_path = get_windows_powershell_shell_path(path); + + #[cfg(not(windows))] let shell_path = get_shell_path(ShellType::PowerShell, path, "pwsh", PWSH_FALLBACK_PATHS) .or_else(|| { get_shell_path( @@ -267,6 +275,41 @@ fn get_powershell_shell(path: Option<&PathBuf>) -> Option { }) } +#[cfg(windows)] +fn get_windows_powershell_shell_path(path: Option<&PathBuf>) -> Option { + if let Some(path) = path.and_then(file_exists) + && !is_windows_batch_script(&path) + { + return Some(path); + } + + let default_shell_path = get_user_shell_path(); + if let Some(default_shell_path) = default_shell_path + && detect_shell_type(&default_shell_path) == Some(ShellType::PowerShell) + && file_exists(&default_shell_path).is_some() + && !is_windows_batch_script(&default_shell_path) + { + return Some(default_shell_path); + } + + try_find_pwsh_executable_blocking() + .map(|path| path.into_path_buf()) + .or_else(|| try_find_powershell_executable_blocking().map(|path| path.into_path_buf())) + .or_else(|| { + POWERSHELL_FALLBACK_PATHS + .iter() + .find_map(|path| file_exists(&PathBuf::from(path))) + }) +} + +#[cfg(windows)] +fn is_windows_batch_script(path: &std::path::Path) -> bool { + matches!( + path.extension().and_then(|extension| extension.to_str()), + Some(extension) if extension.eq_ignore_ascii_case("cmd") || extension.eq_ignore_ascii_case("bat") + ) +} + fn get_cmd_shell(path: Option<&PathBuf>) -> Option { let shell_path = get_shell_path(ShellType::Cmd, path, "cmd", &[]); diff --git a/codex-rs/core/src/shell_tests.rs b/codex-rs/core/src/shell_tests.rs index f0974900b2..8ffe143033 100644 --- a/codex-rs/core/src/shell_tests.rs +++ b/codex-rs/core/src/shell_tests.rs @@ -190,3 +190,14 @@ fn finds_powershell() { assert!(shell_path.ends_with("pwsh.exe") || shell_path.ends_with("powershell.exe")); } + +#[test] +fn windows_batch_shell_wrappers_are_rejected() { + if !cfg!(windows) { + return; + } + + assert!(is_windows_batch_script(std::path::Path::new("pwsh.cmd"))); + assert!(is_windows_batch_script(std::path::Path::new("pwsh.bat"))); + assert!(!is_windows_batch_script(std::path::Path::new("pwsh.exe"))); +}