diff --git a/src/agent/tools/bash.rs b/src/agent/tools/bash.rs index 8482335..e525851 100644 --- a/src/agent/tools/bash.rs +++ b/src/agent/tools/bash.rs @@ -14,6 +14,18 @@ use tokio::io::AsyncReadExt; use super::{ToolDef, ProcessTracker, default_timeout}; +/// RAII guard that SIGTERMs the process group on drop. +/// Ensures child processes are cleaned up when a task is aborted. +struct KillOnDrop(u32); // pid + +impl Drop for KillOnDrop { + fn drop(&mut self) { + if self.0 != 0 { + unsafe { libc::kill(-(self.0 as i32), libc::SIGTERM); } + } + } +} + #[derive(Deserialize)] struct Args { command: String, @@ -60,6 +72,7 @@ pub async fn run_bash(args: &serde_json::Value, tracker: &ProcessTracker) -> Res .with_context(|| format!("Failed to spawn: {}", command))?; let pid = child.id().unwrap_or(0); + let kill_guard = KillOnDrop(pid); tracker.register(pid, command).await; // Take ownership of stdout/stderr handles before waiting, @@ -132,6 +145,8 @@ pub async fn run_bash(args: &serde_json::Value, tracker: &ProcessTracker) -> Res } }; + // Process completed normally — defuse the kill guard + std::mem::forget(kill_guard); tracker.unregister(pid).await; result }