mirror of
https://github.com/openai/codex.git
synced 2026-10-28 16:06:15 +00:00
Fix quoted hook commands on Windows (#33926)
## Why Windows hook commands whose executable paths contain spaces fail when their outer quotes are escaped during argument construction. ## What changed - Pass hook command strings as raw arguments to the default Windows shell. - Do the same for configured shells that use the case-insensitive `/c` flag, while retaining normal argument handling for other shells. ## Testing Add a Windows regression test that runs a quoted `.cmd` hook from a directory with spaces through both the default shell and an explicit `COMSPEC /c` shell. GitOrigin-RevId: 0e7ce44203e01d3823daaaec750c2aeadfcd34b7
This commit is contained in:
@@ -168,9 +168,22 @@ fn build_command(shell: &CommandShell, handler: &ConfiguredHandler) -> Command {
|
||||
Command::new(&shell.program)
|
||||
};
|
||||
if shell.program.is_empty() {
|
||||
#[cfg(windows)]
|
||||
command.raw_arg(format!(r#""{}""#, handler.command));
|
||||
|
||||
#[cfg(not(windows))]
|
||||
command.arg(&handler.command);
|
||||
} else {
|
||||
command.args(&shell.args);
|
||||
|
||||
#[cfg(windows)]
|
||||
if shell.args.iter().any(|arg| arg.eq_ignore_ascii_case("/c")) {
|
||||
command.raw_arg(format!(r#""{}""#, handler.command));
|
||||
} else {
|
||||
command.arg(&handler.command);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
command.arg(&handler.command);
|
||||
}
|
||||
command.envs(&handler.env);
|
||||
@@ -194,3 +207,7 @@ fn default_shell_command() -> Command {
|
||||
command
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows))]
|
||||
#[path = "command_runner_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::HookSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::CommandShell;
|
||||
use super::ConfiguredHandler;
|
||||
use super::run_command;
|
||||
|
||||
#[tokio::test]
|
||||
async fn cmd_shell_runs_quoted_hook_command_path() {
|
||||
let temp = tempdir().expect("create temp dir");
|
||||
let hook_dir = temp.path().join("hook with spaces");
|
||||
fs::create_dir(&hook_dir).expect("create hook dir");
|
||||
let hook_path = hook_dir.join("hook.cmd");
|
||||
fs::write(
|
||||
&hook_path,
|
||||
"@echo off\r\nif not \"%~1\"==\"notify\" exit /B 7\r\necho hook-ran\r\n",
|
||||
)
|
||||
.expect("write hook command");
|
||||
let source_path =
|
||||
AbsolutePathBuf::try_from(hook_path.clone()).expect("absolute hook command path");
|
||||
let handler = ConfiguredHandler {
|
||||
event_name: HookEventName::SessionStart,
|
||||
matcher: None,
|
||||
command: format!(r#""{}" notify"#, hook_path.display()),
|
||||
timeout_sec: 10,
|
||||
status_message: None,
|
||||
source_path,
|
||||
source: HookSource::User,
|
||||
display_order: 0,
|
||||
env: HashMap::new(),
|
||||
};
|
||||
let shells = [
|
||||
CommandShell {
|
||||
program: String::new(),
|
||||
args: Vec::new(),
|
||||
},
|
||||
CommandShell {
|
||||
program: std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string()),
|
||||
args: vec!["/c".to_string()],
|
||||
},
|
||||
];
|
||||
|
||||
for shell in shells {
|
||||
let result = run_command(
|
||||
&shell,
|
||||
&handler,
|
||||
/*configured_order*/ 0,
|
||||
"{}",
|
||||
temp.path(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result.exit_code, Some(0), "stderr: {}", result.stderr);
|
||||
assert_eq!(result.stdout.trim(), "hook-ran");
|
||||
assert!(result.error.is_none());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user