fix(handoff): prevent race condition when killing pane processes
KillPaneProcesses was killing ALL processes in the pane, including the gt handoff process itself. This created a race condition where the process could be killed before RespawnPane executes, causing the pane to close prematurely and requiring manual reattach. Added KillPaneProcessesExcluding() function that excludes specified PIDs from being killed. The handoff command now passes its own PID to avoid the race condition. Fixes: gt-85qd Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user