Compare commits

...

3 commits

Author SHA1 Message Date
Jack Amadeo
b00a99a430 attempt to fix flatpak build
Some checks failed
Cargo Deny / deny (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
2026-06-25 16:03:51 -04:00
Jack Amadeo
5edf7e8f87 Revert "mcp(win): full Windows Job Object cleanup wiring (patch draft) (#9835)"
This reverts commit e944dccf54.
2026-06-25 12:21:45 -04:00
github-actions[bot]
ca3fba75ce chore(release): open release branch for 1.39.0 2026-06-23 18:59:16 +00:00
8 changed files with 24 additions and 140 deletions

View file

@ -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

View file

@ -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;

View file

@ -64,7 +64,7 @@ fn resolve_login_shell_path() -> Option<String> {
.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);

View file

@ -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<HANDLE> {
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
}

View file

@ -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

View file

@ -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())
})?;

View file

@ -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;

View file

@ -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<HANDLE> {
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::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() 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,
};