diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 7a2f484a..53cd1a99 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -8,7 +8,7 @@ ### Fixed - New panes, tabs, layouts, and workspaces using `new_cwd = "follow"` now inherit the foreground process-group leader's working directory instead of an unrelated helper process directory. (#1472) -- Background update checks, captured plugin commands, and workspace Git probes no longer flash console windows on Windows. (#1468) +- Noninteractive update, plugin, integration, sound, custom-command, and Git subprocesses no longer flash console windows on Windows. (#1468) - Live handoff now preserves installed plugins and no longer lets the next plugin installation overwrite the existing registry. (#893) - `herdr wait agent-status` now returns `pane_not_found` promptly when its target pane closes instead of waiting for the full timeout. (#1439) diff --git a/src/cli/plugin.rs b/src/cli/plugin.rs index 68cf0282..32447d1b 100644 --- a/src/cli/plugin.rs +++ b/src/cli/plugin.rs @@ -849,7 +849,7 @@ fn git_checkout( } fn run_git(cwd: Option<&Path>, args: [&str; N]) -> std::io::Result<()> { - let mut command = Command::new("git"); + let mut command = crate::noninteractive_process::command("git"); command.args(args); if let Some(cwd) = cwd { command.current_dir(cwd); @@ -865,7 +865,7 @@ fn run_git(cwd: Option<&Path>, args: [&str; N]) -> std::io::Resu } fn git_output(cwd: &Path, args: [&str; N]) -> std::io::Result { - let output = Command::new("git") + let output = crate::noninteractive_process::command("git") .args(args) .current_dir(cwd) .stdin(Stdio::null()) diff --git a/src/integration/version.rs b/src/integration/version.rs index c9c2af8d..648b86ae 100644 --- a/src/integration/version.rs +++ b/src/integration/version.rs @@ -47,7 +47,7 @@ pub(crate) fn enforce_agent_version( requirement: &AgentVersionRequirement, ) -> io::Result> { let probe = format!("{} {}", requirement.binary, requirement.args.join(" ")); - let output = match std::process::Command::new(requirement.binary) + let output = match crate::noninteractive_process::command(requirement.binary) .args(requirement.args) .output() { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 42b9e128..82a6704d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -26,7 +26,9 @@ pub enum Signal { } pub(crate) fn detached_custom_command_process(command: &str) -> std::process::Command { - detached_custom_command_process_platform(command) + let mut process = detached_custom_command_process_platform(command); + configure_background_command(&mut process); + process } pub(crate) fn pane_custom_command_pty_builder(command: &str) -> portable_pty::CommandBuilder { diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 46c75141..5b0d647f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -671,7 +671,7 @@ mod tests { const CONSOLE_TEST_CHILD_ENV: &str = "HERDR_TEST_CONSOLE_CHILD_MODE"; #[test] - fn background_and_server_daemon_commands_do_not_have_consoles() { + fn windows_background_and_server_daemon_commands_do_not_have_consoles() { if let Some(mode) = std::env::var_os(CONSOLE_TEST_CHILD_ENV) { assert!( unsafe { GetConsoleWindow() }.is_null(), @@ -696,7 +696,7 @@ mod tests { for (mode, configure) in configurations { let mut child = Command::new(&test_exe); child - .arg("background_and_server_daemon_commands_do_not_have_consoles") + .arg("windows_background_and_server_daemon_commands_do_not_have_consoles") .env(CONSOLE_TEST_CHILD_ENV, mode) .stdin(Stdio::null()) .stdout(Stdio::null()) @@ -710,6 +710,22 @@ mod tests { ); } + let command = format!( + r#""{}" windows_background_and_server_daemon_commands_do_not_have_consoles"#, + test_exe.display() + ); + let status = crate::platform::detached_custom_command_process(&command) + .env(CONSOLE_TEST_CHILD_ENV, "detached custom command descendant") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("spawn detached custom command test child"); + assert!( + status.success(), + "detached custom command descendant opened or inherited a console" + ); + if allocated_console { unsafe { FreeConsole(); diff --git a/src/sound.rs b/src/sound.rs index ef45ccc7..7925069c 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -6,7 +6,9 @@ use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::{Command, Output}; +#[cfg(not(windows))] +use std::process::Command; +use std::process::Output; use std::sync::atomic::{AtomicU64, Ordering}; use tracing::warn; @@ -142,7 +144,7 @@ if (-not $script:done) { throw 'sound playback timed out' } #[cfg(windows)] fn run_windows_player(path: &Path) -> Result { - Command::new("powershell.exe") + crate::noninteractive_process::command("powershell.exe") .args([ "-NoLogo", "-NoProfile", diff --git a/src/worktree.rs b/src/worktree.rs index 33b03d56..0bb091d7 100644 --- a/src/worktree.rs +++ b/src/worktree.rs @@ -199,7 +199,7 @@ pub(crate) fn worktree_dirty_remove_message(path: &Path) -> String { #[cfg(any(windows, test))] pub(crate) fn checkout_has_dirty_files(path: &Path) -> Result { let path_arg = path.display().to_string(); - let output = std::process::Command::new("git") + let output = crate::noninteractive_process::command("git") .args([ "-C", &path_arg, @@ -265,7 +265,7 @@ pub(crate) fn build_worktree_add_existing_branch_command( } pub(crate) fn local_branch_exists(repo_root: &Path, branch: &str) -> Result { - let output = std::process::Command::new("git") + let output = crate::noninteractive_process::command("git") .arg("-C") .arg(repo_root) .args(["show-ref", "--verify", "--quiet"]) @@ -306,7 +306,7 @@ pub(crate) fn run_worktree_add_command( } pub(crate) fn run_worktree_command(command: &WorktreeCommand) -> Result<(), String> { - let output = std::process::Command::new(&command.program) + let output = crate::noninteractive_process::command(&command.program) .args(&command.args) .output() .map_err(|err| err.to_string())?; @@ -375,7 +375,7 @@ fn leftover_worktree_checkout_matches_repo(repo_root: &Path, path: &Path) -> boo } fn git_common_worktrees_dir(repo_root: &Path) -> Option { - let output = std::process::Command::new("git") + let output = crate::noninteractive_process::command("git") .arg("-C") .arg(repo_root) .args(["rev-parse", "--git-common-dir"]) @@ -471,7 +471,7 @@ pub(crate) fn parse_worktree_list_porcelain(output: &str) -> Vec Result, String> { - let output = std::process::Command::new("git") + let output = crate::noninteractive_process::command("git") .arg("-C") .arg(repo_root) .args(["worktree", "list", "--porcelain"])