diff --git a/.github/workflows/bundle-desktop-linux.yml b/.github/workflows/bundle-desktop-linux.yml index dd1a88fcc9..edc75d6098 100644 --- a/.github/workflows/bundle-desktop-linux.yml +++ b/.github/workflows/bundle-desktop-linux.yml @@ -148,6 +148,19 @@ jobs: chmod +x ui/desktop/src/bin/goosed ls -la ui/desktop/src/bin/ + - name: Free Rust build artifacts before packaging + run: | + source ./bin/activate-hermit + echo "=== Disk usage before Rust cleanup ===" + df -h + du -sh target .hermit/rust 2>/dev/null || true + + cargo clean + + echo "=== Disk usage after Rust cleanup ===" + df -h + du -sh target .hermit/rust 2>/dev/null || true + - name: Cache pnpm dependencies uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: @@ -169,10 +182,19 @@ jobs: - name: Build Linux packages env: GOOSE_DESKTOP_LINUX_VARIANT: ${{ matrix.variant }} + DEBUG: "@malept/flatpak-bundler,electron-installer-flatpak" run: | source ./bin/activate-hermit cd ui/desktop echo "Building Linux packages (.deb, .rpm, and .flatpak) for ${{ matrix.variant }}..." + trap 'echo "=== Disk usage after Linux packaging failure ==="; df -h; du -sh ../../target ../node_modules node_modules out ~/.local/share/flatpak 2>/dev/null || true' ERR + echo "=== Disk usage before Linux packaging ===" + df -h + du -sh ../../target ../node_modules node_modules out ~/.local/share/flatpak 2>/dev/null || true + echo "=== Flatpak tool versions ===" + flatpak --version + flatpak-builder --version + flatpak remotes --user --show-details || true # Build all configured packages pnpm run make --platform=linux --arch=x64 diff --git a/crates/goose-mcp/src/lib.rs b/crates/goose-mcp/src/lib.rs index f586fde8b2..1bb0b77a7a 100644 --- a/crates/goose-mcp/src/lib.rs +++ b/crates/goose-mcp/src/lib.rs @@ -19,8 +19,6 @@ 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 a8f66980cd..30399f75ff 100644 --- a/crates/goose-mcp/src/subprocess.rs +++ b/crates/goose-mcp/src/subprocess.rs @@ -64,7 +64,7 @@ fn resolve_login_shell_path() -> Option { .stdout(Stdio::piped()) .stderr(Stdio::null()); - // Spawn in a new session so that interactive shells job-control setup + // Spawn in a new session so that interactive shell job-control setup // cannot steal the terminal foreground from the parent goose process. cmd.wrap(ProcessSession); diff --git a/crates/goose-mcp/src/windows_job.rs b/crates/goose-mcp/src/windows_job.rs deleted file mode 100644 index 67ee7a4cee..0000000000 --- a/crates/goose-mcp/src/windows_job.rs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 -} diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index 294bcaee95..9dc486e063 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -221,7 +221,7 @@ subtle = { version = "2.5", default-features = false, features = ["std"] } gethostname = "1.1.0" [target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3.9", default-features = false, features = ["wincred", "std", "jobapi2", "winbase", "winnt", "processthreadsapi", "handleapi", "minwindef"] } +winapi = { workspace = true } keyring = { workspace = true, features = ["windows-native"], optional = true } # Platform-specific GPU acceleration for Whisper and local inference diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index ec2fcc4895..995a32488e 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -353,15 +353,6 @@ 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 fecde77134..86805ffa1e 100644 --- a/crates/goose/src/lib.rs +++ b/crates/goose/src/lib.rs @@ -46,11 +46,8 @@ 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 deleted file mode 100644 index 14ace73396..0000000000 --- a/crates/goose/src/windows_job.rs +++ /dev/null @@ -1,102 +0,0 @@ -// 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_QUOTA, 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 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) - { - 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 { - // 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() { - if AssignProcessToJobObject(job, proc) == FALSE { - tracing::warn!(pid, "failed to assign child process to Windows job object"); - } - 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, -};