fix(handoff): preserve tmux session by setting remain-on-exit before kill
When gt handoff killed pane processes before respawning, the pane would be destroyed (since remain-on-exit defaults to off), causing respawn-pane to fail with "can't find pane" error. Fix: Set remain-on-exit=on before killing processes, so the pane survives process death and can be respawned. This restores tmux session reuse on handoffs. Changes: - Add SetRemainOnExit method to tmux package - Call SetRemainOnExit(true) before KillPaneProcesses in: - Local handoff (runHandoff) - Remote handoff (handoffRemoteSession) - Mayor attach respawn (runMayorAttach) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,13 @@ func runHandoff(cmd *cobra.Command, args []string) error {
|
||||
_ = os.WriteFile(markerPath, []byte(currentSession), 0644)
|
||||
}
|
||||
|
||||
// Set remain-on-exit so the pane survives process death during handoff.
|
||||
// Without this, killing processes causes tmux to destroy the pane before
|
||||
// we can respawn it. This is essential for tmux session reuse.
|
||||
if err := t.SetRemainOnExit(pane, true); err != nil {
|
||||
style.PrintWarning("could not set remain-on-exit: %v", err)
|
||||
}
|
||||
|
||||
// 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(pane); err != nil {
|
||||
@@ -212,6 +219,7 @@ func runHandoff(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Use exec to respawn the pane - this kills us and restarts
|
||||
// Note: respawn-pane automatically resets remain-on-exit to off
|
||||
return t.RespawnPane(pane, restartCmd)
|
||||
}
|
||||
|
||||
@@ -567,6 +575,13 @@ func handoffRemoteSession(t *tmux.Tmux, targetSession, restartCmd string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set remain-on-exit so the pane survives process death during handoff.
|
||||
// Without this, killing processes causes tmux to destroy the pane before
|
||||
// we can respawn it. This is essential for tmux session reuse.
|
||||
if err := t.SetRemainOnExit(targetPane, true); err != nil {
|
||||
style.PrintWarning("could not set remain-on-exit: %v", err)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -581,6 +596,7 @@ func handoffRemoteSession(t *tmux.Tmux, targetSession, restartCmd string) error
|
||||
}
|
||||
|
||||
// Respawn the remote session's pane
|
||||
// Note: respawn-pane automatically resets remain-on-exit to off
|
||||
if err := t.RespawnPane(targetPane, restartCmd); err != nil {
|
||||
return fmt.Errorf("respawning pane: %w", err)
|
||||
}
|
||||
|
||||
@@ -200,6 +200,12 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
|
||||
// Set remain-on-exit so the pane survives process death during respawn.
|
||||
// Without this, killing processes causes tmux to destroy the pane.
|
||||
if err := t.SetRemainOnExit(paneID, true); err != nil {
|
||||
style.PrintWarning("could not set remain-on-exit: %v", err)
|
||||
}
|
||||
|
||||
// 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(paneID); err != nil {
|
||||
@@ -207,6 +213,7 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
||||
style.PrintWarning("could not kill pane processes: %v", err)
|
||||
}
|
||||
|
||||
// Note: respawn-pane automatically resets remain-on-exit to off
|
||||
if err := t.RespawnPane(paneID, startupCmd); err != nil {
|
||||
return fmt.Errorf("restarting runtime: %w", err)
|
||||
}
|
||||
|
||||
@@ -1385,6 +1385,19 @@ func (t *Tmux) ClearHistory(pane string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRemainOnExit controls whether a pane stays around after its process exits.
|
||||
// When on, the pane remains with "[Exited]" status, allowing respawn-pane to restart it.
|
||||
// When off (default), the pane is destroyed when its process exits.
|
||||
// This is essential for handoff: set on before killing processes, so respawn-pane works.
|
||||
func (t *Tmux) SetRemainOnExit(pane string, on bool) error {
|
||||
value := "on"
|
||||
if !on {
|
||||
value = "off"
|
||||
}
|
||||
_, err := t.run("set-option", "-t", pane, "remain-on-exit", value)
|
||||
return err
|
||||
}
|
||||
|
||||
// SwitchClient switches the current tmux client to a different session.
|
||||
// Used after remote recycle to move the user's view to the recycled session.
|
||||
func (t *Tmux) SwitchClient(targetSession string) error {
|
||||
|
||||
Reference in New Issue
Block a user