From 93380a6fac0e97e5f653d3bf591c503d8bfd05d6 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 2 Apr 2026 13:47:10 -0700 Subject: [PATCH] fix: add shell fallback paths for pwsh/powershell that work on GitHub Actions Windows runners (#16617) Recently, I merged a number of PRs to increase startup timeouts for scripts that ran under PowerShell, but in the failure for `suite::codex_tool::test_shell_command_approval_triggers_elicitation`, I found this in the error logs when running on Bazel with BuildBuddy: ``` [mcp stderr] 2026-04-02T19:54:10.758951Z ERROR codex_core::tools::router: error=Exit code: 1 [mcp stderr] Wall time: 0.2 seconds [mcp stderr] Output: [mcp stderr] 'New-Item' is not recognized as an internal or external command, [mcp stderr] operable program or batch file. [mcp stderr] ``` This error implies that the command was run under `cmd.exe` instead of `pwsh.exe`. Under GitHub Actions, I suspect that the `%PATH%` that is passed to our Bazel builder is scrubbed such that our tests cannot find PowerShell where GitHub installs it. Having these explicit fallback paths should help. While we could enable these only for tests, I don't see any harm in keeping them in production, as well. --- codex-rs/core/src/shell.rs | 48 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/codex-rs/core/src/shell.rs b/codex-rs/core/src/shell.rs index 194371256..48c760d66 100644 --- a/codex-rs/core/src/shell.rs +++ b/codex-rs/core/src/shell.rs @@ -166,7 +166,7 @@ fn get_shell_path( shell_type: ShellType, provided_path: Option<&PathBuf>, binary_name: &str, - fallback_paths: Vec<&str>, + fallback_paths: &[&str], ) -> Option { // If exact provided path exists, use it if provided_path.and_then(file_exists).is_some() { @@ -197,8 +197,10 @@ fn get_shell_path( None } +const ZSH_FALLBACK_PATHS: &[&str] = &["/bin/zsh"]; + fn get_zsh_shell(path: Option<&PathBuf>) -> Option { - let shell_path = get_shell_path(ShellType::Zsh, path, "zsh", vec!["/bin/zsh"]); + let shell_path = get_shell_path(ShellType::Zsh, path, "zsh", ZSH_FALLBACK_PATHS); shell_path.map(|shell_path| Shell { shell_type: ShellType::Zsh, @@ -207,8 +209,10 @@ fn get_zsh_shell(path: Option<&PathBuf>) -> Option { }) } +const BASH_FALLBACK_PATHS: &[&str] = &["/bin/bash"]; + fn get_bash_shell(path: Option<&PathBuf>) -> Option { - let shell_path = get_shell_path(ShellType::Bash, path, "bash", vec!["/bin/bash"]); + let shell_path = get_shell_path(ShellType::Bash, path, "bash", BASH_FALLBACK_PATHS); shell_path.map(|shell_path| Shell { shell_type: ShellType::Bash, @@ -217,8 +221,10 @@ fn get_bash_shell(path: Option<&PathBuf>) -> Option { }) } +const SH_FALLBACK_PATHS: &[&str] = &["/bin/sh"]; + fn get_sh_shell(path: Option<&PathBuf>) -> Option { - let shell_path = get_shell_path(ShellType::Sh, path, "sh", vec!["/bin/sh"]); + let shell_path = get_shell_path(ShellType::Sh, path, "sh", SH_FALLBACK_PATHS); shell_path.map(|shell_path| Shell { shell_type: ShellType::Sh, @@ -227,14 +233,32 @@ fn get_sh_shell(path: Option<&PathBuf>) -> Option { }) } +// Note the `pwsh` and `powershell` fallback paths are where the respective +// shells are commonly installed on GitHub Actions Windows runners, but may not +// be present on all Windows machines: +// https://docs.github.com/en/actions/tutorials/build-and-test-code/powershell + +#[cfg(windows)] +const PWSH_FALLBACK_PATHS: &[&str] = &[r#"C:\Program Files\PowerShell\7\pwsh.exe"#]; +#[cfg(not(windows))] +const PWSH_FALLBACK_PATHS: &[&str] = &["/usr/local/bin/pwsh"]; + +#[cfg(windows)] +const POWERSHELL_FALLBACK_PATHS: &[&str] = + &[r#"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"#]; +#[cfg(not(windows))] +const POWERSHELL_FALLBACK_PATHS: &[&str] = &[]; + fn get_powershell_shell(path: Option<&PathBuf>) -> Option { - let shell_path = get_shell_path( - ShellType::PowerShell, - path, - "pwsh", - vec!["/usr/local/bin/pwsh"], - ) - .or_else(|| get_shell_path(ShellType::PowerShell, path, "powershell", vec![])); + let shell_path = get_shell_path(ShellType::PowerShell, path, "pwsh", PWSH_FALLBACK_PATHS) + .or_else(|| { + get_shell_path( + ShellType::PowerShell, + path, + "powershell", + POWERSHELL_FALLBACK_PATHS, + ) + }); shell_path.map(|shell_path| Shell { shell_type: ShellType::PowerShell, @@ -244,7 +268,7 @@ fn get_powershell_shell(path: Option<&PathBuf>) -> Option { } fn get_cmd_shell(path: Option<&PathBuf>) -> Option { - let shell_path = get_shell_path(ShellType::Cmd, path, "cmd", vec![]); + let shell_path = get_shell_path(ShellType::Cmd, path, "cmd", &[]); shell_path.map(|shell_path| Shell { shell_type: ShellType::Cmd,