From 30876229a034e77bee4dd53f2443f8a3b2b9c93a Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 11:43:25 +1000 Subject: [PATCH 01/11] mcp(windows): add Windows Job Object cleanup scaffolding; patch to attach subprocesses to Job Object (branch micn/mcp-win-cleanup) --- crates/goose-mcp/src/subprocess.rs | 3 +- crates/goose-mcp/src/windows_job.rs | 79 +++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 crates/goose-mcp/src/windows_job.rs diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs index 30399f75ff88..3006596a15e1 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -1,5 +1,6 @@ use std::sync::OnceLock; -use tokio::process::Command; +use tokio::process::Command;\n#[cfg(windows)] use crate::windows_job;\n#[cfg(windows)] use crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; + #[cfg(windows)] const CREATE_NO_WINDOW_FLAG: u32 = 0x08000000; diff --git a/crates/goose-mcp/src/windows_job.rs b/crates/goose-mcp/src/windows_job.rs new file mode 100644 index 000000000000..0e528aaa306f --- /dev/null +++ b/crates/goose-mcp/src/windows_job.rs @@ -0,0 +1,79 @@ +// Windows-specific MCP child cleanup via Job Object +// This module provides a minimal Windows API surface to prepare for +// cleaning up MCP child processes by attaching them to a Job Object +// that is terminated when the parent Goose/MCP process exits. +// +// Note: This file is Windows-only and guarded by cfg(windows). + +#![allow(dead_code)] + +#[cfg(windows)] +mod windows_impl { + use std::mem::{size_of, zeroed}; + use std::ptr::null_mut; + use std::sync::OnceLock; + + use winapi::shared::minwindef::FALSE; + use winapi::um::handleapi::CloseHandle; + use winapi::um::processthreadsapi::OpenProcess; + use winapi::um::jobapi2::{AssignProcessToJobObject, CreateJobObjectW}; + use winapi::um::winbase::SetInformationJobObject; + use winapi::um::winnt::{HANDLE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_TERMINATE, PROCESS_SET_INFORMATION}; + + static JOB_HANDLE: OnceLock = OnceLock::new(); + + pub fn ensure_job_object() -> Option { + JOB_HANDLE.get_or_try_init(|| { + unsafe { + // Create a new Job Object + let job = CreateJobObjectW(null_mut(), null_mut()); + if job.is_null() { + return Err(std::io::Error::last_os_error()); + } + + // Enable the Kill-On-Job-Close flag so all processes in the job are terminated + let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = zeroed(); + // BasicLimitInformation is where we set LimitFlags + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + // Apply the information to the job object + // SAFETY: Pass the correct information class and a pointer to the information struct + let _ = SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + &mut info as *mut _ as *mut _, + size_of::() as u32, + ); + + Ok(job) + } + }).ok().cloned() + } + + pub fn attach_pid_to_job(pid: u32) { + if let Some(job) = JOB_HANDLE.get() { + unsafe { + // Open the target process with the rights we need + let proc = OpenProcess(PROCESS_TERMINATE | PROCESS_SET_INFORMATION, FALSE, pid); + if !proc.is_null() { + let _ = AssignProcessToJobObject(*job, proc); + CloseHandle(proc); + } + } + } + } + + pub fn init_windows_cleanup() { + let _ = ensure_job_object(); + } + + pub fn windows_cleanup_enabled() -> bool { + JOB_HANDLE.get().is_some() + } +} + +#[cfg(windows)] +pub use windows_impl::{ensure_job_object, attach_pid_to_job, init_windows_cleanup, windows_cleanup_enabled}; + +#[cfg(not(windows))] +compile_error!("windows_job.rs is Windows-only."); From 1e3881975f0093d0ec488a4dfa5de5fd4f3327ff Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 11:57:47 +1000 Subject: [PATCH 02/11] mcp(windows): fix broken subprocess.rs import header (remove literal \n), guard windows imports --- crates/goose-mcp/src/subprocess.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs index 3006596a15e1..9b414fac0897 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -1,5 +1,5 @@ use std::sync::OnceLock; -use tokio::process::Command;\n#[cfg(windows)] use crate::windows_job;\n#[cfg(windows)] use crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; +use tokio::process::Command;\n#[cfg(windows)]\nuse crate::windows_job;\n#[cfg(windows)]\nuse crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; #[cfg(windows)] From 89e9f22a2218614b3d383d10d952810c6c759ec8 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 11:59:21 +1000 Subject: [PATCH 03/11] mcp(windows): fix broken imports in subprocess.rs; gate Windows code with cfg(windows); add proper path resolution scaffolding --- crates/goose-mcp/src/subprocess.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs index 9b414fac0897..3e6d37dd2b0f 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -1,5 +1,9 @@ use std::sync::OnceLock; -use tokio::process::Command;\n#[cfg(windows)]\nuse crate::windows_job;\n#[cfg(windows)]\nuse crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; +use tokio::process::Command; +#[cfg(windows)] +use crate::windows_job; +#[cfg(windows)] +use crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; #[cfg(windows)] @@ -65,7 +69,7 @@ fn resolve_login_shell_path() -> Option { .stdout(Stdio::piped()) .stderr(Stdio::null()); - // Spawn in a new session so that interactive shell job-control setup + // Spawn in a new session so that interactive shells job-control setup // cannot steal the terminal foreground from the parent goose process. cmd.wrap(ProcessSession); From e85430aec33357483d87db725b6e6241f38baa04 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:09:51 +1000 Subject: [PATCH 04/11] mcp-windows: remove stale crates/goose-mcp/windows_job.rs duplicate; gate with cfg(windows) --- crates/goose-mcp/src/lib.rs | 2 + crates/goose-mcp/src/subprocess.rs | 7 +- crates/goose-mcp/src/windows_job.rs | 79 ----------------- crates/goose/src/agents/extension_manager.rs | 9 ++ crates/goose/src/lib.rs | 3 + crates/goose/src/windows_job.rs | 90 ++++++++++++++++++++ 6 files changed, 107 insertions(+), 83 deletions(-) delete mode 100644 crates/goose-mcp/src/windows_job.rs create mode 100644 crates/goose/src/windows_job.rs diff --git a/crates/goose-mcp/src/lib.rs b/crates/goose-mcp/src/lib.rs index 1bb0b77a7a0b..f586fde8b209 100644 --- a/crates/goose-mcp/src/lib.rs +++ b/crates/goose-mcp/src/lib.rs @@ -19,6 +19,8 @@ mod memory; pub mod peekaboo; pub mod subprocess; pub mod tutorial; +#[cfg(windows)] +pub mod windows_job; pub use autovisualiser::AutoVisualiserRouter; pub use computercontroller::ComputerControllerServer; diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs index 3e6d37dd2b0f..3513da38a22b 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -1,10 +1,9 @@ -use std::sync::OnceLock; -use tokio::process::Command; #[cfg(windows)] use crate::windows_job; #[cfg(windows)] -use crate::windows_job::{init_windows_cleanup, ensure_job_object, attach_pid_to_job}; - +use crate::windows_job::{attach_pid_to_job, ensure_job_object, init_windows_cleanup}; +use std::sync::OnceLock; +use tokio::process::Command; #[cfg(windows)] const CREATE_NO_WINDOW_FLAG: u32 = 0x08000000; diff --git a/crates/goose-mcp/src/windows_job.rs b/crates/goose-mcp/src/windows_job.rs deleted file mode 100644 index 0e528aaa306f..000000000000 --- a/crates/goose-mcp/src/windows_job.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Windows-specific MCP child cleanup via Job Object -// This module provides a minimal Windows API surface to prepare for -// cleaning up MCP child processes by attaching them to a Job Object -// that is terminated when the parent Goose/MCP process exits. -// -// Note: This file is Windows-only and guarded by cfg(windows). - -#![allow(dead_code)] - -#[cfg(windows)] -mod windows_impl { - use std::mem::{size_of, zeroed}; - use std::ptr::null_mut; - use std::sync::OnceLock; - - use winapi::shared::minwindef::FALSE; - use winapi::um::handleapi::CloseHandle; - use winapi::um::processthreadsapi::OpenProcess; - use winapi::um::jobapi2::{AssignProcessToJobObject, CreateJobObjectW}; - use winapi::um::winbase::SetInformationJobObject; - use winapi::um::winnt::{HANDLE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_TERMINATE, PROCESS_SET_INFORMATION}; - - static JOB_HANDLE: OnceLock = OnceLock::new(); - - pub fn ensure_job_object() -> Option { - JOB_HANDLE.get_or_try_init(|| { - unsafe { - // Create a new Job Object - let job = CreateJobObjectW(null_mut(), null_mut()); - if job.is_null() { - return Err(std::io::Error::last_os_error()); - } - - // Enable the Kill-On-Job-Close flag so all processes in the job are terminated - let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = zeroed(); - // BasicLimitInformation is where we set LimitFlags - info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - - // Apply the information to the job object - // SAFETY: Pass the correct information class and a pointer to the information struct - let _ = SetInformationJobObject( - job, - JobObjectExtendedLimitInformation, - &mut info as *mut _ as *mut _, - size_of::() as u32, - ); - - Ok(job) - } - }).ok().cloned() - } - - pub fn attach_pid_to_job(pid: u32) { - if let Some(job) = JOB_HANDLE.get() { - unsafe { - // Open the target process with the rights we need - let proc = OpenProcess(PROCESS_TERMINATE | PROCESS_SET_INFORMATION, FALSE, pid); - if !proc.is_null() { - let _ = AssignProcessToJobObject(*job, proc); - CloseHandle(proc); - } - } - } - } - - pub fn init_windows_cleanup() { - let _ = ensure_job_object(); - } - - pub fn windows_cleanup_enabled() -> bool { - JOB_HANDLE.get().is_some() - } -} - -#[cfg(windows)] -pub use windows_impl::{ensure_job_object, attach_pid_to_job, init_windows_cleanup, windows_cleanup_enabled}; - -#[cfg(not(windows))] -compile_error!("windows_job.rs is Windows-only."); diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index cf7f67641f05..04d7fc80b12c 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -353,6 +353,15 @@ async fn child_process_client( let (transport, mut stderr) = TokioChildProcess::builder(command) .stderr(Stdio::piped()) .spawn()?; + // Attach the child to a Windows Job Object to ensure proper cleanup on Goose exit + #[cfg(windows)] + { + if let Some(pid) = transport.id() { + // Initialize Job Object and attach the child process to it + crate::windows_job::init_windows_cleanup(); + crate::windows_job::attach_pid_to_job(pid); + } + } let mut stderr = stderr.take().ok_or_else(|| { ExtensionError::SetupError("failed to attach child process stderr".to_owned()) })?; diff --git a/crates/goose/src/lib.rs b/crates/goose/src/lib.rs index c610d4a78e02..612dc387c1c7 100644 --- a/crates/goose/src/lib.rs +++ b/crates/goose/src/lib.rs @@ -45,8 +45,11 @@ pub mod slash_commands; pub mod source_roots; pub mod sources; pub mod subprocess; + pub mod token_counter; pub mod tool_inspection; pub mod tool_monitor; pub mod tracing; pub mod utils; +#[cfg(windows)] +pub mod windows_job; diff --git a/crates/goose/src/windows_job.rs b/crates/goose/src/windows_job.rs new file mode 100644 index 000000000000..6c3b5ef75fed --- /dev/null +++ b/crates/goose/src/windows_job.rs @@ -0,0 +1,90 @@ +// Windows Job Object cleanup for child processes (Goose Windows support). +// +// Attaches spawned MCP subprocesses to a Job Object configured with +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. When the Goose process exits (and the +// job handle is closed by the OS), Windows terminates every process in the +// job, preventing orphaned child processes. This is the Windows analog of the +// Linux PR_SET_PDEATHSIG behavior in subprocess.rs. + +#![allow(dead_code)] + +#[cfg(windows)] +mod windows_impl { + use std::mem::{size_of, zeroed}; + use std::ptr::null_mut; + use std::sync::atomic::{AtomicUsize, Ordering}; + + use winapi::shared::minwindef::FALSE; + use winapi::um::handleapi::CloseHandle; + use winapi::um::jobapi2::{AssignProcessToJobObject, CreateJobObjectW}; + use winapi::um::processthreadsapi::OpenProcess; + use winapi::um::winbase::SetInformationJobObject; + use winapi::um::winnt::{ + JobObjectExtendedLimitInformation, HANDLE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_INFORMATION, PROCESS_TERMINATE, + }; + + // HANDLE (*mut c_void) is not Send/Sync, so we store the handle as a usize + // and cast back to HANDLE at use sites. 0 means "not yet created". + static JOB_HANDLE: AtomicUsize = AtomicUsize::new(0); + + pub fn ensure_job_object() -> Option { + let existing = JOB_HANDLE.load(Ordering::Acquire); + if existing != 0 { + return Some(existing as HANDLE); + } + + unsafe { + let job = CreateJobObjectW(null_mut(), null_mut()); + if job.is_null() { + return None; + } + + let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = zeroed(); + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + let _ = SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + &mut info as *mut _ as *mut _, + size_of::() as u32, + ); + + // Publish the handle, but if another thread won the race, close ours. + match JOB_HANDLE.compare_exchange(0, job as usize, Ordering::AcqRel, Ordering::Acquire) + { + Ok(_) => Some(job), + Err(winner) => { + CloseHandle(job); + Some(winner as HANDLE) + } + } + } + } + + pub fn attach_pid_to_job(pid: u32) { + let job = match ensure_job_object() { + Some(job) => job, + None => return, + }; + unsafe { + let proc = OpenProcess(PROCESS_TERMINATE | PROCESS_SET_INFORMATION, FALSE, pid); + if !proc.is_null() { + let _ = AssignProcessToJobObject(job, proc); + CloseHandle(proc); + } + } + } + + pub fn init_windows_cleanup() { + let _ = ensure_job_object(); + } + + pub fn windows_cleanup_enabled() -> bool { + JOB_HANDLE.load(Ordering::Acquire) != 0 + } +} + +#[cfg(windows)] +pub use windows_impl::{ + attach_pid_to_job, ensure_job_object, init_windows_cleanup, windows_cleanup_enabled, +}; From a3081592e936a43b2c736c13ea8064538958045d Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:10:08 +1000 Subject: [PATCH 05/11] mcp-windows: replace duplicates with stub to satisfy compile while Goose crate provides real Windows job wrap --- crates/goose-mcp/src/windows_job.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 crates/goose-mcp/src/windows_job.rs diff --git a/crates/goose-mcp/src/windows_job.rs b/crates/goose-mcp/src/windows_job.rs new file mode 100644 index 000000000000..cfc302b7f7da --- /dev/null +++ b/crates/goose-mcp/src/windows_job.rs @@ -0,0 +1,18 @@ +// Stub Windows Job Object helpers for MCP path (no-ops on Windows). +// This file provides the minimal surface required by crates/goose-mcp/src/subprocess.rs +// when built on Windows. The real Windows Job Object integration lives in the goose crate. + +#[cfg(windows)] +pub type HANDLE = *mut std::ffi::c_void; + +#[cfg(windows)] +pub fn ensure_job_object() -> Option { None } + +#[cfg(windows)] +pub fn attach_pid_to_job(_pid: u32) {} + +#[cfg(windows)] +pub fn init_windows_cleanup() {} + +#[cfg(windows)] +pub fn windows_cleanup_enabled() -> bool { false } From 90c946da2d8ce65cc37211402f0e85f8667b5440 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:11:32 +1000 Subject: [PATCH 06/11] windows: extend winapi features for Windows target (jobapi2, winbase, winnt, processthreadsapi, handleapi, minwindef) --- crates/goose-mcp/src/windows_job.rs | 8 ++++++-- crates/goose/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/goose-mcp/src/windows_job.rs b/crates/goose-mcp/src/windows_job.rs index cfc302b7f7da..67ee7a4cee51 100644 --- a/crates/goose-mcp/src/windows_job.rs +++ b/crates/goose-mcp/src/windows_job.rs @@ -6,7 +6,9 @@ pub type HANDLE = *mut std::ffi::c_void; #[cfg(windows)] -pub fn ensure_job_object() -> Option { None } +pub fn ensure_job_object() -> Option { + None +} #[cfg(windows)] pub fn attach_pid_to_job(_pid: u32) {} @@ -15,4 +17,6 @@ pub fn attach_pid_to_job(_pid: u32) {} pub fn init_windows_cleanup() {} #[cfg(windows)] -pub fn windows_cleanup_enabled() -> bool { false } +pub fn windows_cleanup_enabled() -> bool { + false +} diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index 47f172bda99b..b743a55b79af 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -219,7 +219,7 @@ subtle = { version = "2.5", default-features = false, features = ["std"] } gethostname = "1.1.0" [target.'cfg(target_os = "windows")'.dependencies] -winapi = { workspace = true } +winapi = { version = "0.3.9", default-features = false, features = ["wincred", "std", "jobapi2", "winbase", "winnt", "processthreadsapi", "handleapi", "minwindef"] } keyring = { workspace = true, features = ["windows-native"], optional = true } # Platform-specific GPU acceleration for Whisper and local inference From a6b8dd44cb81fdba89cb247289d09b849bbf3b01 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:13:34 +1000 Subject: [PATCH 07/11] mcp: remove Windows job imports from subprocess.rs; MCP path no longer uses windows_job --- crates/goose-mcp/src/subprocess.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/goose-mcp/src/subprocess.rs b/crates/goose-mcp/src/subprocess.rs index 3513da38a22b..a8f66980cdea 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -1,7 +1,3 @@ -#[cfg(windows)] -use crate::windows_job; -#[cfg(windows)] -use crate::windows_job::{attach_pid_to_job, ensure_job_object, init_windows_cleanup}; use std::sync::OnceLock; use tokio::process::Command; From cff4229782aeeca38627fe0f018376bbe6ca38aa Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:24:26 +1000 Subject: [PATCH 08/11] windows: bail if SetInformationJobObject fails, dont publish partial job handle Signed-off-by: Michael Neale --- crates/goose/src/windows_job.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/windows_job.rs b/crates/goose/src/windows_job.rs index 6c3b5ef75fed..936cf5fc7fea 100644 --- a/crates/goose/src/windows_job.rs +++ b/crates/goose/src/windows_job.rs @@ -42,12 +42,19 @@ mod windows_impl { let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = zeroed(); info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - let _ = SetInformationJobObject( + let set_res = SetInformationJobObject( job, JobObjectExtendedLimitInformation, &mut info as *mut _ as *mut _, size_of::() as u32, ); + if set_res == FALSE { + // If we fail to configure the job object to terminate on close, do not publish + // this handle. Cleaning up here avoids mutating global state with a partially + // configured Job Object. + CloseHandle(job); + return None; + } // Publish the handle, but if another thread won the race, close ours. match JOB_HANDLE.compare_exchange(0, job as usize, Ordering::AcqRel, Ordering::Acquire) From 71d4660f2e9a31f2cee5c9d5f043ceba696e0215 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:28:22 +1000 Subject: [PATCH 09/11] ci: trigger re-run after Windows fix From 1aa1e6e011846dbef974929105588ecbdd413b1d Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 17 Jun 2026 13:33:25 +1000 Subject: [PATCH 10/11] ci: trigger re-run after Windows fix From 03f090942507c63649fdc4d81c2b9d0aa228bf5b Mon Sep 17 00:00:00 2001 From: Douwe M Osinga Date: Thu, 18 Jun 2026 08:32:11 -0400 Subject: [PATCH 11/11] windows: request PROCESS_SET_QUOTA and check job assignment result AssignProcessToJobObject requires PROCESS_SET_QUOTA on the process handle in addition to PROCESS_TERMINATE. The handle was opened with PROCESS_SET_INFORMATION instead, so the assignment silently failed and child processes were never tied to the job object, defeating the cleanup. Also check the assign result and warn on failure. Signed-off-by: Douwe M Osinga --- crates/goose/src/windows_job.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/goose/src/windows_job.rs b/crates/goose/src/windows_job.rs index 936cf5fc7fea..14ace73396d2 100644 --- a/crates/goose/src/windows_job.rs +++ b/crates/goose/src/windows_job.rs @@ -21,7 +21,7 @@ mod windows_impl { use winapi::um::winbase::SetInformationJobObject; use winapi::um::winnt::{ JobObjectExtendedLimitInformation, HANDLE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_INFORMATION, PROCESS_TERMINATE, + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_QUOTA, PROCESS_TERMINATE, }; // HANDLE (*mut c_void) is not Send/Sync, so we store the handle as a usize @@ -74,9 +74,14 @@ mod windows_impl { None => return, }; unsafe { - let proc = OpenProcess(PROCESS_TERMINATE | PROCESS_SET_INFORMATION, FALSE, pid); + // AssignProcessToJobObject requires PROCESS_SET_QUOTA in addition to + // PROCESS_TERMINATE; without it the assignment fails and the child is + // never tied to the job, leaving it orphaned on exit. + let proc = OpenProcess(PROCESS_TERMINATE | PROCESS_SET_QUOTA, FALSE, pid); if !proc.is_null() { - let _ = AssignProcessToJobObject(job, proc); + if AssignProcessToJobObject(job, proc) == FALSE { + tracing::warn!(pid, "failed to assign child process to Windows job object"); + } CloseHandle(proc); } }