From e32e74d20bbfd308607a095c3c495c8dc3789a8e Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Mon, 16 Feb 2026 10:58:30 -0800 Subject: [PATCH] fix: ensure shell processes are properly terminated --- pkg/tools/shell.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index 1ca3fc35a..6cf5b71f4 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -10,6 +10,7 @@ import ( "regexp" "runtime" "strings" + "syscall" "time" ) @@ -97,6 +98,7 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *To cmd = exec.CommandContext(cmdCtx, "powershell", "-NoProfile", "-NonInteractive", "-Command", command) } else { cmd = exec.CommandContext(cmdCtx, "sh", "-c", command) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} } if cwd != "" { cmd.Dir = cwd @@ -106,7 +108,31 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *To cmd.Stdout = &stdout cmd.Stderr = &stderr - err := cmd.Run() + if err := cmd.Start(); err != nil { + return ErrorResult(fmt.Sprintf("failed to start command: %v", err)) + } + + done := make(chan error, 1) + go func() { done <- cmd.Wait() }() + + var err error + select { + case err = <-done: + case <-cmdCtx.Done(): + if cmd.Process != nil { + if runtime.GOOS != "windows" { + if pgid, pgErr := syscall.Getpgid(cmd.Process.Pid); pgErr == nil { + _ = syscall.Kill(-pgid, syscall.SIGKILL) + } else { + _ = cmd.Process.Kill() + } + } else { + _ = cmd.Process.Kill() + } + } + err = <-done + } + output := stdout.String() if stderr.Len() > 0 { output += "\nSTDERR:\n" + stderr.String()