From e57297cb1b1d6248d2d47b776e0c4cb55f4c4141 Mon Sep 17 00:00:00 2001 From: mayor Date: Wed, 21 Jan 2026 22:46:00 -0800 Subject: [PATCH] fix(crew): don't kill pane processes when creating new session KillPaneProcesses was being called on new sessions before respawn, which killed the fresh shell and destroyed the pane. This caused "can't find pane" errors on session creation. Now KillPaneProcesses is only called when restarting in an existing session where Claude/Node processes might be running and ignoring SIGHUP. For new sessions, we just use respawn-pane directly. Also added retry limit and error checking for the stale session recovery path. Co-Authored-By: Claude Opus 4.5 --- internal/cmd/crew_at.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/internal/cmd/crew_at.go b/internal/cmd/crew_at.go index 06a56a78..d8a6918d 100644 --- a/internal/cmd/crew_at.go +++ b/internal/cmd/crew_at.go @@ -16,6 +16,9 @@ import ( "github.com/steveyegge/gastown/internal/workspace" ) +// crewAtRetried tracks if we've already retried after stale session cleanup +var crewAtRetried bool + func runCrewAt(cmd *cobra.Command, args []string) error { var name string @@ -210,19 +213,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error { if runtimeConfig.Session != nil && runtimeConfig.Session.ConfigDirEnv != "" && claudeConfigDir != "" { startupCmd = config.PrependEnv(startupCmd, map[string]string{runtimeConfig.Session.ConfigDirEnv: claudeConfigDir}) } - // 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 { - // Non-fatal but log the warning - style.PrintWarning("could not kill pane processes: %v", err) - } + // Note: Don't call KillPaneProcesses here - this is a NEW session with just + // a fresh shell. Killing it would destroy the pane before we can respawn. + // KillPaneProcesses is only needed when restarting in an EXISTING session + // where Claude/Node processes might be running and ignoring SIGHUP. if err := t.RespawnPane(paneID, startupCmd); err != nil { - // If pane is stale (session exists but pane doesn't), recreate the session - if strings.Contains(err.Error(), "can't find pane") { - fmt.Printf("Stale session detected, recreating...\n") - _ = t.KillSession(sessionID) - return runCrewAt(cmd, args) // Retry with fresh session - } return fmt.Errorf("starting runtime: %w", err) } @@ -274,8 +269,15 @@ func runCrewAt(cmd *cobra.Command, args []string) error { if err := t.RespawnPane(paneID, startupCmd); err != nil { // If pane is stale (session exists but pane doesn't), recreate the session if strings.Contains(err.Error(), "can't find pane") { + if crewAtRetried { + return fmt.Errorf("stale session persists after cleanup: %w", err) + } fmt.Printf("Stale session detected, recreating...\n") - _ = t.KillSession(sessionID) + if killErr := t.KillSession(sessionID); killErr != nil { + return fmt.Errorf("failed to kill stale session: %w", killErr) + } + crewAtRetried = true + defer func() { crewAtRetried = false }() return runCrewAt(cmd, args) // Retry with fresh session } return fmt.Errorf("restarting runtime: %w", err)