diff --git a/internal/cmd/witness.go b/internal/cmd/witness.go index 6a4876ee..7b94ade1 100644 --- a/internal/cmd/witness.go +++ b/internal/cmd/witness.go @@ -284,7 +284,8 @@ func witnessSessionName(rigName string) string { } // ensureWitnessSession creates a witness tmux session if it doesn't exist. -// Returns true if a new session was created, false if it already existed. +// Returns true if a new session was created, false if it already existed (and is healthy). +// Implements 'ensure' semantics: if session exists but Claude is dead (zombie), kills and recreates. func ensureWitnessSession(rigName string, r *rig.Rig) (bool, error) { t := tmux.NewTmux() sessionName := witnessSessionName(rigName) @@ -296,7 +297,16 @@ func ensureWitnessSession(rigName string, r *rig.Rig) (bool, error) { } if running { - return false, nil + // Session exists - check if Claude is actually running (healthy vs zombie) + if t.IsClaudeRunning(sessionName) { + // Healthy - Claude is running + return false, nil + } + // Zombie - tmux alive but Claude dead. Kill and recreate. + fmt.Printf("%s Detected zombie session (tmux alive, Claude dead). Recreating...\n", style.Dim.Render("⚠")) + if err := t.KillSession(sessionName); err != nil { + return false, fmt.Errorf("killing zombie session: %w", err) + } } // Working directory is the witness's rig clone (if it exists) or witness dir diff --git a/internal/refinery/manager.go b/internal/refinery/manager.go index 674f627a..dc1fb8ef 100644 --- a/internal/refinery/manager.go +++ b/internal/refinery/manager.go @@ -137,7 +137,16 @@ func (m *Manager) Start(foreground bool) error { // Background mode: check if session already exists running, _ := t.HasSession(sessionID) if running { - return ErrAlreadyRunning + // Session exists - check if Claude is actually running (healthy vs zombie) + if t.IsClaudeRunning(sessionID) { + // Healthy - Claude is running + return ErrAlreadyRunning + } + // Zombie - tmux alive but Claude dead. Kill and recreate. + fmt.Fprintln(m.output, "⚠ Detected zombie session (tmux alive, Claude dead). Recreating...") + if err := t.KillSession(sessionID); err != nil { + return fmt.Errorf("killing zombie session: %w", err) + } } // Also check via PID for backwards compatibility