diff --git a/internal/tmux/tmux.go b/internal/tmux/tmux.go index 21c428f8..74bd4ca3 100644 --- a/internal/tmux/tmux.go +++ b/internal/tmux/tmux.go @@ -447,40 +447,57 @@ func (t *Tmux) KillPaneProcessesExcluding(pane string, excludePIDs []string) err return fmt.Errorf("pane PID is empty") } - // Get all descendant PIDs recursively (returns deepest-first order) - descendants := getAllDescendants(pid) + // Collect PIDs to kill (excluding specified ones) + toKill := make(map[string]bool) - // Filter out excluded PIDs - var filtered []string - for _, dpid := range descendants { - if !exclude[dpid] { - filtered = append(filtered, dpid) + // First, collect process group members (catches reparented processes) + pgid := getProcessGroupID(pid) + if pgid != "" && pgid != "0" && pgid != "1" { + for _, member := range getProcessGroupMembers(pgid) { + if !exclude[member] { + toKill[member] = true + } } } - // Send SIGTERM to all non-excluded descendants (deepest first to avoid orphaning) - for _, dpid := range filtered { + // Also walk the process tree for any descendants that might have called setsid() + descendants := getAllDescendants(pid) + for _, dpid := range descendants { + if !exclude[dpid] { + toKill[dpid] = true + } + } + + // Convert to slice for iteration + var killList []string + for dpid := range toKill { + killList = append(killList, dpid) + } + + // Send SIGTERM to all non-excluded processes + for _, dpid := range killList { _ = exec.Command("kill", "-TERM", dpid).Run() } - // Wait for graceful shutdown - time.Sleep(100 * time.Millisecond) + // Wait for graceful shutdown (2s gives processes time to clean up) + time.Sleep(processKillGracePeriod) - // Send SIGKILL to any remaining non-excluded descendants - for _, dpid := range filtered { + // Send SIGKILL to any remaining non-excluded processes + for _, dpid := range killList { _ = exec.Command("kill", "-KILL", dpid).Run() } // Kill the pane process itself only if not excluded if !exclude[pid] { _ = exec.Command("kill", "-TERM", pid).Run() - time.Sleep(100 * time.Millisecond) + time.Sleep(processKillGracePeriod) _ = exec.Command("kill", "-KILL", pid).Run() } return nil } + // KillServer terminates the entire tmux server and all sessions. func (t *Tmux) KillServer() error { _, err := t.run("kill-server")