diff --git a/internal/cmd/handoff.go b/internal/cmd/handoff.go index 785d92c0..f6b29a80 100644 --- a/internal/cmd/handoff.go +++ b/internal/cmd/handoff.go @@ -204,17 +204,17 @@ func runHandoff(cmd *cobra.Command, args []string) error { _ = os.WriteFile(markerPath, []byte(currentSession), 0644) } - // Kill all processes in the pane before respawning to prevent orphan leaks - // RespawnPane's -k flag only sends SIGHUP which Claude/Node may ignore - // IMPORTANT: Exclude our own process to avoid race condition where we get killed - // before RespawnPane executes, causing the pane to close prematurely (gt-85qd) - myPID := fmt.Sprintf("%d", os.Getpid()) - if err := t.KillPaneProcessesExcluding(pane, []string{myPID}); err != nil { - // Non-fatal but log the warning - style.PrintWarning("could not kill pane processes: %v", err) - } + // NOTE: We intentionally do NOT kill pane processes before respawning (hq-bv7ef). + // Previous approach (KillPaneProcessesExcluding) killed the pane's main process, + // which caused the pane to close (remain-on-exit is off by default), making + // RespawnPane fail because the target pane no longer exists. + // + // The respawn-pane -k flag handles killing atomically - it kills the old process + // and starts the new one in a single operation without closing the pane. + // If orphan processes remain (e.g., Claude ignoring SIGHUP), they will be cleaned + // up when the new session starts or when the Witness runs periodic cleanup. - // Use exec to respawn the pane - this kills us and restarts + // Use respawn-pane to atomically kill old process and start new one return t.RespawnPane(pane, restartCmd) } @@ -570,12 +570,10 @@ func handoffRemoteSession(t *tmux.Tmux, targetSession, restartCmd string) error return nil } - // Kill all processes in the pane before respawning to prevent orphan leaks - // RespawnPane's -k flag only sends SIGHUP which Claude/Node may ignore - if err := t.KillPaneProcesses(targetPane); err != nil { - // Non-fatal but log the warning - style.PrintWarning("could not kill pane processes: %v", err) - } + // NOTE: We intentionally do NOT kill pane processes before respawning (hq-bv7ef). + // Previous approach (KillPaneProcesses) killed the pane's main process, which caused + // the pane to close (remain-on-exit is off by default), making RespawnPane fail. + // The respawn-pane -k flag handles killing atomically without closing the pane. // Clear scrollback history before respawn (resets copy-mode from [0/N] to [0/0]) if err := t.ClearHistory(targetPane); err != nil { @@ -583,7 +581,7 @@ func handoffRemoteSession(t *tmux.Tmux, targetSession, restartCmd string) error style.PrintWarning("could not clear history: %v", err) } - // Respawn the remote session's pane + // Respawn the remote session's pane - -k flag atomically kills old process and starts new one if err := t.RespawnPane(targetPane, restartCmd); err != nil { return fmt.Errorf("respawning pane: %w", err) }