From de1eb0a923f6c5b768ac49f0425b7213a89301b7 Mon Sep 17 00:00:00 2001 From: soryu Date: Mon, 26 Jan 2026 22:41:18 +0000 Subject: Terminate all processes on makima CLI SIGTERM --- makima/src/daemon/process/claude.rs | 78 ++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 31 deletions(-) (limited to 'makima/src/daemon/process') diff --git a/makima/src/daemon/process/claude.rs b/makima/src/daemon/process/claude.rs index f3aa421..c8add1c 100644 --- a/makima/src/daemon/process/claude.rs +++ b/makima/src/daemon/process/claude.rs @@ -117,19 +117,21 @@ impl ClaudeProcess { self.child.id() } - /// Send SIGTERM to gracefully terminate the process (Unix only). + /// Send SIGTERM to gracefully terminate the process and all its children (Unix only). /// Returns Ok(true) if signal was sent, Ok(false) if process already exited. + /// Sends signal to the entire process group (negative PID) to kill all children. #[cfg(unix)] pub fn terminate(&self) -> Result { - use nix::sys::signal::{kill, Signal}; + use nix::sys::signal::{killpg, Signal}; use nix::unistd::Pid; if let Some(pid) = self.child.id() { - match kill(Pid::from_raw(pid as i32), Signal::SIGTERM) { + // Kill the entire process group (the process is its own group leader) + match killpg(Pid::from_raw(pid as i32), Signal::SIGTERM) { Ok(()) => Ok(true), - Err(nix::errno::Errno::ESRCH) => Ok(false), // Process doesn't exist + Err(nix::errno::Errno::ESRCH) => Ok(false), // Process group doesn't exist Err(e) => Err(ClaudeProcessError::OutputRead(format!( - "Failed to send SIGTERM: {}", + "Failed to send SIGTERM to process group: {}", e ))), } @@ -552,27 +554,34 @@ impl ProcessManager { (self.claude_command.clone(), claude_args) }; - // Spawn the process - let mut child = Command::new(&command) - .args(&args) + // Spawn the process in its own process group so we can kill all children + let mut cmd = Command::new(&command); + cmd.args(&args) .current_dir(working_dir) .envs(env) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - if use_bubblewrap { - ClaudeProcessError::BubblewrapNotFound - } else { - ClaudeProcessError::CommandNotFound(self.claude_command.clone()) - } + .kill_on_drop(true); + + // On Unix, create a new process group so we can kill all child processes + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + cmd.process_group(0); + } + + let mut child = cmd.spawn().map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + if use_bubblewrap { + ClaudeProcessError::BubblewrapNotFound } else { - ClaudeProcessError::SpawnFailed(e) + ClaudeProcessError::CommandNotFound(self.claude_command.clone()) } - })?; + } else { + ClaudeProcessError::SpawnFailed(e) + } + })?; // Create output channel let (tx, rx) = mpsc::channel(1000); @@ -730,23 +739,30 @@ impl ProcessManager { tracing::debug!(args = ?args, "Claude continue command arguments"); - // Spawn the process - let mut child = Command::new(&self.claude_command) - .args(&args) + // Spawn the process in its own process group so we can kill all children + let mut cmd = Command::new(&self.claude_command); + cmd.args(&args) .current_dir(working_dir) .envs(env) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - ClaudeProcessError::CommandNotFound(self.claude_command.clone()) - } else { - ClaudeProcessError::SpawnFailed(e) - } - })?; + .kill_on_drop(true); + + // On Unix, create a new process group so we can kill all child processes + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + cmd.process_group(0); + } + + let mut child = cmd.spawn().map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + ClaudeProcessError::CommandNotFound(self.claude_command.clone()) + } else { + ClaudeProcessError::SpawnFailed(e) + } + })?; // Create output channel let (tx, rx) = mpsc::channel(1000); -- cgit v1.2.3