Suppress Windows console windows for background subprocesses (#49164)

## Why

Background helpers launched from a detached Windows process can allocate console windows. Job Object launches and containment fallbacks need to preserve console suppression as well.

## What changed

- Add `codex_utils_process::background_command` to set `CREATE_NO_WINDOW` on Windows, and use it across background subprocess launches, including Git, plugins, authentication, diagnostics, and sandbox helpers.
- Centralize console suppression in Job Object launches. Keep `CREATE_NO_WINDOW` when retrying without containment, and clear `CREATE_SUSPENDED` so the fallback child can run.
- Preserve console inheritance for interactive shell launches and interactive AWS authentication refreshes.

## Testing

Add Windows regression tests covering console-free launches from detached parents through standard and Tokio commands, stdout/stderr capture, successful containment, Job Object creation and assignment failures, and sandbox setup refresh helpers.

GitOrigin-RevId: b2c238fa7d4f9fbd868e5adf7d4be9958ff01e6c
This commit is contained in:
Eric Traut
2026-09-29 04:38:37 +00:00
committed by copyberry
parent 3226512d47
commit fbc169827e
44 changed files with 380 additions and 71 deletions
+11 -12
View File
@@ -3,6 +3,8 @@ use std::ffi::OsStr;
use std::ffi::OsString;
use std::future::Future;
use std::io::ErrorKind;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::path::Path;
#[cfg(not(unix))]
use std::process::Stdio;
@@ -27,6 +29,9 @@ use tokio::task::JoinSet;
use tokio::time::timeout;
use tracing::Span;
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
use super::CommandShell;
use super::ConfiguredHandler;
use super::ConfiguredHandlerKind;
@@ -221,18 +226,9 @@ pub(crate) async fn run_command(
command.current_dir(cwd);
#[cfg(windows)]
let mut process_tree_job = JobObject::create().ok();
#[cfg(windows)]
let child = match process_tree_job.as_ref() {
Some(job) => match job.spawn_contained(&mut command) {
Ok(child) => Ok(child),
Err(_) => {
process_tree_job = None;
command.creation_flags(0);
command.spawn()
}
},
None => command.spawn(),
let (child, process_tree_job) = match JobObject::spawn_background(&mut command) {
Ok((child, job)) => (Ok(child), job),
Err(error) => (Err(error), None),
};
#[cfg(not(windows))]
let child = command.spawn();
@@ -353,6 +349,7 @@ impl Drop for ProcessTreeGuard {
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.creation_flags(CREATE_NO_WINDOW)
.spawn();
}
}
@@ -414,6 +411,8 @@ fn build_command(
#[cfg(unix)]
command.process_mode(codex_utils_pty::ProcessMode::NewSession);
#[cfg(windows)]
command.creation_flags(CREATE_NO_WINDOW);
#[cfg(not(unix))]
command
.env_clear()