feat(tmux): add KillSessionWithProcesses for explicit process termination
Before calling tmux kill-session, explicitly kill the pane's process tree using pkill. This ensures claude processes don't survive session termination due to SIGHUP being caught/ignored. Implementation: - Add KillSessionWithProcesses() to tmux.go - Update killSessionsInOrder() in start.go to use new method - Update stopSession() in down.go to use new method Fixes: gt-5r7zr Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -387,8 +387,8 @@ func stopSession(t *tmux.Tmux, sessionName string) (bool, error) {
|
|||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill the session
|
// Kill the session (with explicit process termination to prevent orphans)
|
||||||
return true, t.KillSession(sessionName)
|
return true, t.KillSessionWithProcesses(sessionName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// acquireShutdownLock prevents concurrent shutdowns.
|
// acquireShutdownLock prevents concurrent shutdowns.
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ func killSessionsInOrder(t *tmux.Tmux, sessions []string, mayorSession, deaconSe
|
|||||||
|
|
||||||
// 1. Stop Deacon first
|
// 1. Stop Deacon first
|
||||||
if inList(deaconSession) {
|
if inList(deaconSession) {
|
||||||
if err := t.KillSession(deaconSession); err == nil {
|
if err := t.KillSessionWithProcesses(deaconSession); err == nil {
|
||||||
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), deaconSession)
|
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), deaconSession)
|
||||||
stopped++
|
stopped++
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,7 @@ func killSessionsInOrder(t *tmux.Tmux, sessions []string, mayorSession, deaconSe
|
|||||||
if sess == deaconSession || sess == mayorSession {
|
if sess == deaconSession || sess == mayorSession {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := t.KillSession(sess); err == nil {
|
if err := t.KillSessionWithProcesses(sess); err == nil {
|
||||||
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), sess)
|
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), sess)
|
||||||
stopped++
|
stopped++
|
||||||
}
|
}
|
||||||
@@ -646,7 +646,7 @@ func killSessionsInOrder(t *tmux.Tmux, sessions []string, mayorSession, deaconSe
|
|||||||
|
|
||||||
// 3. Stop Mayor last
|
// 3. Stop Mayor last
|
||||||
if inList(mayorSession) {
|
if inList(mayorSession) {
|
||||||
if err := t.KillSession(mayorSession); err == nil {
|
if err := t.KillSessionWithProcesses(mayorSession); err == nil {
|
||||||
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), mayorSession)
|
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), mayorSession)
|
||||||
stopped++
|
stopped++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,39 @@ func (t *Tmux) KillSession(name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KillSessionWithProcesses explicitly kills all processes in a session before terminating it.
|
||||||
|
// This prevents orphaned processes that can occur when Claude catches/ignores SIGHUP.
|
||||||
|
// The kill sequence is:
|
||||||
|
// 1. Get the pane's PID
|
||||||
|
// 2. Send SIGTERM to the process group (all children)
|
||||||
|
// 3. Wait 100ms for graceful exit
|
||||||
|
// 4. Send SIGKILL if processes still alive
|
||||||
|
// 5. Kill the tmux session
|
||||||
|
func (t *Tmux) KillSessionWithProcesses(name string) error {
|
||||||
|
// Get the pane PID
|
||||||
|
pid, err := t.GetPanePID(name)
|
||||||
|
if err != nil {
|
||||||
|
// Session might not exist or be in bad state, try direct kill
|
||||||
|
return t.KillSession(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid != "" {
|
||||||
|
// Send SIGTERM to process group (all children of the pane process)
|
||||||
|
termCmd := exec.Command("pkill", "-TERM", "-P", pid)
|
||||||
|
_ = termCmd.Run() // Ignore errors - process might already be dead
|
||||||
|
|
||||||
|
// Wait for graceful termination
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Send SIGKILL to any survivors
|
||||||
|
killCmd := exec.Command("pkill", "-KILL", "-P", pid)
|
||||||
|
_ = killCmd.Run() // Ignore errors - process might already be dead
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now kill the tmux session
|
||||||
|
return t.KillSession(name)
|
||||||
|
}
|
||||||
|
|
||||||
// KillServer terminates the entire tmux server and all sessions.
|
// KillServer terminates the entire tmux server and all sessions.
|
||||||
func (t *Tmux) KillServer() error {
|
func (t *Tmux) KillServer() error {
|
||||||
_, err := t.run("kill-server")
|
_, err := t.run("kill-server")
|
||||||
|
|||||||
Reference in New Issue
Block a user