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 <noreply@anthropic.com>
This commit is contained in:
mayor
2026-01-21 22:46:00 -08:00
committed by beads/crew/emma
parent dff6c3fb3c
commit e57297cb1b

View File

@@ -16,6 +16,9 @@ import (
"github.com/steveyegge/gastown/internal/workspace" "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 { func runCrewAt(cmd *cobra.Command, args []string) error {
var name string var name string
@@ -210,19 +213,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
if runtimeConfig.Session != nil && runtimeConfig.Session.ConfigDirEnv != "" && claudeConfigDir != "" { if runtimeConfig.Session != nil && runtimeConfig.Session.ConfigDirEnv != "" && claudeConfigDir != "" {
startupCmd = config.PrependEnv(startupCmd, map[string]string{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 // Note: Don't call KillPaneProcesses here - this is a NEW session with just
// RespawnPane's -k flag only sends SIGHUP which Claude/Node may ignore // a fresh shell. Killing it would destroy the pane before we can respawn.
if err := t.KillPaneProcesses(paneID); err != nil { // KillPaneProcesses is only needed when restarting in an EXISTING session
// Non-fatal but log the warning // where Claude/Node processes might be running and ignoring SIGHUP.
style.PrintWarning("could not kill pane processes: %v", err)
}
if err := t.RespawnPane(paneID, startupCmd); err != nil { 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) 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 err := t.RespawnPane(paneID, startupCmd); err != nil {
// If pane is stale (session exists but pane doesn't), recreate the session // If pane is stale (session exists but pane doesn't), recreate the session
if strings.Contains(err.Error(), "can't find pane") { 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") 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 runCrewAt(cmd, args) // Retry with fresh session
} }
return fmt.Errorf("restarting runtime: %w", err) return fmt.Errorf("restarting runtime: %w", err)