From fb47728f1b39a73bdc701b7e5890c39f11df2260 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 11 Feb 2026 12:43:18 -0500 Subject: [PATCH] More no-window flags (#7122) --- crates/goose-cli/src/recipes/github_recipe.rs | 9 ++++++ crates/goose-cli/src/session/output.rs | 3 ++ .../goose-mcp/src/computercontroller/mod.rs | 3 ++ .../computercontroller/platform/windows.rs | 2 ++ crates/goose-mcp/src/developer/paths.rs | 3 ++ crates/goose-mcp/src/developer/shell.rs | 3 ++ crates/goose-mcp/src/lib.rs | 1 + crates/goose-mcp/src/subprocess.rs | 30 +++++++++++++++++++ crates/goose/src/agents/retry.rs | 4 +++ crates/goose/src/posthog.rs | 3 ++ crates/goose/src/providers/azureauth.rs | 2 ++ crates/goose/src/subprocess.rs | 30 +++++++++++++++++-- 12 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 crates/goose-mcp/src/subprocess.rs diff --git a/crates/goose-cli/src/recipes/github_recipe.rs b/crates/goose-cli/src/recipes/github_recipe.rs index bfe297d8dd..f19c3a60d8 100644 --- a/crates/goose-cli/src/recipes/github_recipe.rs +++ b/crates/goose-cli/src/recipes/github_recipe.rs @@ -5,8 +5,10 @@ use goose::recipe::RECIPE_FILE_EXTENSIONS; use serde::{Deserialize, Serialize}; use goose::recipe::read_recipe_file_content::RecipeFile; +use goose::subprocess::SubprocessExt; use std::env; use std::fs; + use std::path::Path; use std::path::PathBuf; use std::process::Command; @@ -104,6 +106,7 @@ pub fn ensure_gh_authenticated() -> Result<()> { // Check authentication status let status = Command::new("gh") .args(["auth", "status"]) + .set_no_window() .status() .map_err(|_| { anyhow::anyhow!("Failed to run `gh auth status`. Make sure you have `gh` installed.") @@ -151,6 +154,7 @@ fn ensure_repo_cloned(recipe_repo_full_name: &str) -> Result { let status = Command::new("gh") .args(["repo", "clone", recipe_repo_full_name]) .current_dir(local_repo_parent_path.clone()) + .set_no_window() .status() .map_err(|_: std::io::Error| anyhow::anyhow!(error_message.clone()))?; @@ -167,6 +171,7 @@ fn fetch_origin(local_repo_path: &Path) -> Result<()> { let status = Command::new("git") .args(["fetch", "origin"]) .current_dir(local_repo_path) + .set_no_window() .status() .map_err(|_| anyhow::anyhow!(error_message.clone()))?; @@ -190,6 +195,7 @@ fn get_folder_from_github(local_repo_path: &Path, recipe_name: &str) -> Result

Result> { // Get repository contents using GitHub CLI let output = Command::new("gh") .args(["api", &format!("repos/{}/contents", repo)]) + .set_no_window() .output() .map_err(|e| anyhow!("Failed to fetch repository contents using 'gh api' command (executed when GOOSE_RECIPE_GITHUB_REPO is configured). This requires GitHub CLI (gh) to be installed and authenticated. Error: {}", e))?; @@ -269,6 +276,7 @@ fn check_github_directory_for_recipe(repo: &str, dir_name: &str) -> Result "api", &format!("repos/{}/contents/{}/{}", repo, dir_name, recipe_filename), ]) + .set_no_window() .output() .map_err(|e| anyhow!("Failed to get recipe file content: {}", e))?; diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 8b539c710b..f1343e1bf3 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -6,6 +6,8 @@ use goose::conversation::message::{ ActionRequiredData, Message, MessageContent, ToolRequest, ToolResponse, }; use goose::providers::canonical::maybe_get_canonical_model; +#[cfg(target_os = "windows")] +use goose::subprocess::SubprocessExt; use goose::utils::safe_truncate; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use rmcp::model::{CallToolRequestParams, JsonObject, PromptArgument}; @@ -179,6 +181,7 @@ pub fn run_status_hook(status: &str) { .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) + .set_no_window() .status(); #[cfg(not(target_os = "windows"))] diff --git a/crates/goose-mcp/src/computercontroller/mod.rs b/crates/goose-mcp/src/computercontroller/mod.rs index 69e69ad8f0..785d612578 100644 --- a/crates/goose-mcp/src/computercontroller/mod.rs +++ b/crates/goose-mcp/src/computercontroller/mod.rs @@ -1,3 +1,4 @@ +use crate::subprocess::SubprocessExt; use etcetera::{choose_app_strategy, AppStrategy}; use indoc::{formatdoc, indoc}; use reqwest::{Client, Url}; @@ -710,6 +711,7 @@ impl ComputerControllerServer { .arg(&command) .env("GOOSE_TERMINAL", "1") .env("AGENT", "goose") + .set_no_window() .output() .await .map_err(|e| { @@ -725,6 +727,7 @@ impl ComputerControllerServer { .arg(&command) .env("GOOSE_TERMINAL", "1") .env("AGENT", "goose") + .set_no_window() .output() .await .map_err(|e| { diff --git a/crates/goose-mcp/src/computercontroller/platform/windows.rs b/crates/goose-mcp/src/computercontroller/platform/windows.rs index 95dc317046..fe241b1237 100644 --- a/crates/goose-mcp/src/computercontroller/platform/windows.rs +++ b/crates/goose-mcp/src/computercontroller/platform/windows.rs @@ -1,4 +1,5 @@ use super::SystemAutomation; +use crate::subprocess::SubprocessExt; use std::path::PathBuf; use std::process::Command; @@ -13,6 +14,7 @@ impl SystemAutomation for WindowsAutomation { .arg(script) .env("GOOSE_TERMINAL", "1") .env("AGENT", "goose") + .set_no_window() .output()?; Ok(String::from_utf8_lossy(&output.stdout).into_owned()) diff --git a/crates/goose-mcp/src/developer/paths.rs b/crates/goose-mcp/src/developer/paths.rs index 29d3f2a0f3..9eb500d77a 100644 --- a/crates/goose-mcp/src/developer/paths.rs +++ b/crates/goose-mcp/src/developer/paths.rs @@ -1,3 +1,4 @@ +use crate::subprocess::SubprocessExt; use anyhow::Result; use std::env; use std::path::PathBuf; @@ -81,12 +82,14 @@ async fn get_windows_path_async(shell: &str) -> Result { "pwsh" | "powershell" => { Command::new(shell) .args(["-NoLogo", "-Command", "$env:PATH"]) + .set_no_window() .output() .await } _ => { Command::new(shell) .args(["/c", "echo %PATH%"]) + .set_no_window() .output() .await } diff --git a/crates/goose-mcp/src/developer/shell.rs b/crates/goose-mcp/src/developer/shell.rs index fcbcbc7714..9196a48b40 100644 --- a/crates/goose-mcp/src/developer/shell.rs +++ b/crates/goose-mcp/src/developer/shell.rs @@ -1,3 +1,4 @@ +use crate::subprocess::SubprocessExt; use std::{env, ffi::OsString, process::Stdio}; #[cfg(unix)] @@ -111,6 +112,7 @@ pub fn configure_shell_command( working_dir: Option<&std::path::Path>, ) -> tokio::process::Command { let mut command_builder = tokio::process::Command::new(&shell_config.executable); + command_builder.set_no_window(); if let Some(dir) = working_dir { command_builder.current_dir(dir); @@ -177,6 +179,7 @@ pub async fn kill_process_group( // Use taskkill to kill the process tree on Windows let _kill_result = tokio::process::Command::new("taskkill") .args(&["/F", "/T", "/PID", &pid.to_string()]) + .set_no_window() .output() .await; } diff --git a/crates/goose-mcp/src/lib.rs b/crates/goose-mcp/src/lib.rs index 4b4a540691..f9c47a5b82 100644 --- a/crates/goose-mcp/src/lib.rs +++ b/crates/goose-mcp/src/lib.rs @@ -14,6 +14,7 @@ pub mod computercontroller; pub mod developer; pub mod mcp_server_runner; mod memory; +pub mod subprocess; pub mod tutorial; pub use autovisualiser::AutoVisualiserRouter; diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs new file mode 100644 index 0000000000..b2dfec6eb6 --- /dev/null +++ b/crates/goose-mcp/src/subprocess.rs @@ -0,0 +1,30 @@ +use tokio::process::Command; + +#[cfg(windows)] +const CREATE_NO_WINDOW_FLAG: u32 = 0x08000000; + +pub trait SubprocessExt { + fn set_no_window(&mut self) -> &mut Self; +} + +impl SubprocessExt for Command { + fn set_no_window(&mut self) -> &mut Self { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + self.creation_flags(CREATE_NO_WINDOW_FLAG); + } + self + } +} + +impl SubprocessExt for std::process::Command { + fn set_no_window(&mut self) -> &mut Self { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + self.creation_flags(CREATE_NO_WINDOW_FLAG); + } + self + } +} diff --git a/crates/goose/src/agents/retry.rs b/crates/goose/src/agents/retry.rs index 71f6238622..7f62fec0e8 100644 --- a/crates/goose/src/agents/retry.rs +++ b/crates/goose/src/agents/retry.rs @@ -6,6 +6,8 @@ use tokio::process::Command; use tokio::sync::Mutex; use tracing::{debug, info, warn}; +use crate::subprocess::SubprocessExt; + use crate::agents::types::SessionConfig; use crate::agents::types::{ RetryConfig, SuccessCheck, DEFAULT_ON_FAILURE_TIMEOUT_SECONDS, DEFAULT_RETRY_TIMEOUT_SECONDS, @@ -243,6 +245,8 @@ pub async fn execute_shell_command( cmd }; + cmd.set_no_window(); + let output = cmd .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/crates/goose/src/posthog.rs b/crates/goose/src/posthog.rs index 9c94820d82..a1449a9a67 100644 --- a/crates/goose/src/posthog.rs +++ b/crates/goose/src/posthog.rs @@ -4,6 +4,8 @@ use crate::config::paths::Paths; use crate::config::{get_enabled_extensions, Config}; use crate::session::session_manager::CURRENT_SCHEMA_VERSION; use crate::session::SessionManager; +#[cfg(target_os = "windows")] +use crate::subprocess::SubprocessExt; use chrono::{DateTime, Utc}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -141,6 +143,7 @@ fn get_platform_version() -> Option { { std::process::Command::new("cmd") .args(["/C", "ver"]) + .set_no_window() .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) diff --git a/crates/goose/src/providers/azureauth.rs b/crates/goose/src/providers/azureauth.rs index be7e39f40c..894d0cd717 100644 --- a/crates/goose/src/providers/azureauth.rs +++ b/crates/goose/src/providers/azureauth.rs @@ -1,3 +1,4 @@ +use crate::subprocess::SubprocessExt; use chrono; use serde::Deserialize; use std::sync::Arc; @@ -134,6 +135,7 @@ impl AzureAuth { "--resource", "https://cognitiveservices.azure.com", ]) + .set_no_window() .output() .await .map_err(|e| AuthError::TokenExchange(format!("Failed to execute Azure CLI: {}", e)))?; diff --git a/crates/goose/src/subprocess.rs b/crates/goose/src/subprocess.rs index d7bdfcaede..7e97dfad3d 100644 --- a/crates/goose/src/subprocess.rs +++ b/crates/goose/src/subprocess.rs @@ -3,13 +3,37 @@ use tokio::process::Command; #[cfg(windows)] const CREATE_NO_WINDOW_FLAG: u32 = 0x08000000; +pub trait SubprocessExt { + fn set_no_window(&mut self) -> &mut Self; +} + +impl SubprocessExt for Command { + fn set_no_window(&mut self) -> &mut Self { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + self.creation_flags(CREATE_NO_WINDOW_FLAG); + } + self + } +} + +impl SubprocessExt for std::process::Command { + fn set_no_window(&mut self) -> &mut Self { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + self.creation_flags(CREATE_NO_WINDOW_FLAG); + } + self + } +} + #[allow(unused_variables)] pub fn configure_subprocess(command: &mut Command) { // Isolate subprocess into its own process group so it does not receive // SIGINT when the user presses Ctrl+C in the terminal. #[cfg(unix)] command.process_group(0); - - #[cfg(windows)] - command.creation_flags(CREATE_NO_WINDOW_FLAG); + command.set_no_window(); }