From cff47cb1d554be15a7afa58ed1dc5a554fa84864 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 19 Nov 2025 04:08:18 -0800 Subject: [PATCH] fix(windows) parse powershell commands --- codex-rs/core/src/parse_command.rs | 55 +++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/parse_command.rs b/codex-rs/core/src/parse_command.rs index f335347042..ed71bd8c5f 100644 --- a/codex-rs/core/src/parse_command.rs +++ b/codex-rs/core/src/parse_command.rs @@ -919,6 +919,51 @@ mod tests { }], ); } + + #[test] + fn powershell_cd_is_stripped() { + assert_parsed( + &vec_str(&["powershell", "-Command", "cd foo; Get-ChildItem"]), + vec![ParsedCommand::Unknown { + cmd: "Get-ChildItem".to_string(), + }], + ); + } + #[test] + fn powershell_pipes_are_preserved() { + assert_parsed( + &vec_str(&[ + "powershell", + "-Command", + "Get-Content core/src/parse_command.rs | Select-Object -First 980 | Select-Object -Last 140", + ]), + vec![ + ParsedCommand::Unknown { + cmd: "Get-Content core/src/parse_command.rs".to_string(), + }, + ParsedCommand::Unknown { + cmd: "Select-Object -First 980".to_string(), + }, + ParsedCommand::Unknown { + cmd: "Select-Object -Last 140".to_string(), + }, + ], + ); + } + + #[test] + fn powershell_at_escaping_is_preserved() { + assert_parsed( + &vec_str(&[ + "powershell", + "-Command", + "@'\\nprint('Hello, world!')\\n'@ | python -", + ]), + vec![ParsedCommand::Unknown { + cmd: "@'\\nprint('Hello, world!')\\n'@ | python -".to_string(), + }], + ); + } } pub fn parse_command_impl(command: &[String]) -> Vec { @@ -926,12 +971,6 @@ pub fn parse_command_impl(command: &[String]) -> Vec { return commands; } - if let Some((_, script)) = extract_powershell_command(command) { - return vec![ParsedCommand::Unknown { - cmd: script.to_string(), - }]; - } - let normalized = normalize_tokens(command); let parts = if contains_connectors(&normalized) { @@ -1238,8 +1277,8 @@ fn parse_find_query_and_path(tail: &[String]) -> (Option, Option } fn parse_shell_lc_commands(original: &[String]) -> Option> { - // Only handle bash/zsh here; PowerShell is stripped separately without bash parsing. - let (_, script) = extract_bash_command(original)?; + let (_, script) = + extract_bash_command(original).or_else(|| extract_powershell_command(original))?; if let Some(tree) = try_parse_shell(script) && let Some(all_commands) = try_parse_word_only_commands_sequence(&tree, script)