From 15d1dc8fa80bd2fa20b27ff9a39bc1098dd19257 Mon Sep 17 00:00:00 2001 From: aleiby Date: Sat, 17 Jan 2026 00:00:53 -0800 Subject: [PATCH] fix: Make WaitForCommand/WaitForRuntimeReady fatal in manager Start() (#529) Fixes #525: gt up reports deacon success but session doesn't actually start Previously, WaitForCommand failures were marked as "non-fatal" in the manager Start() methods used by gt up. This caused gt up to report success even when Claude failed to start, because the error was silently ignored. Now when WaitForCommand or WaitForRuntimeReady times out: 1. The zombie tmux session is killed 2. An error is returned to the caller 3. gt up properly reports the failure This aligns the manager Start() behavior with the cmd start functions (e.g., gt deacon start) which already had fatal WaitForCommand behavior. Changed files: - internal/deacon/manager.go - internal/mayor/manager.go - internal/witness/manager.go - internal/refinery/manager.go Co-authored-by: Claude Opus 4.5 --- internal/deacon/manager.go | 6 ++++-- internal/mayor/manager.go | 6 ++++-- internal/refinery/manager.go | 6 ++++-- internal/witness/manager.go | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/internal/deacon/manager.go b/internal/deacon/manager.go index d4e45aa6..ec16bbf7 100644 --- a/internal/deacon/manager.go +++ b/internal/deacon/manager.go @@ -106,9 +106,11 @@ func (m *Manager) Start(agentOverride string) error { theme := tmux.DeaconTheme() _ = t.ConfigureGasTownSession(sessionID, theme, "", "Deacon", "health-check") - // Wait for Claude to start (non-fatal) + // Wait for Claude to start - fatal if Claude fails to launch if err := t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil { - // Non-fatal - try to continue anyway + // Kill the zombie session before returning error + _ = t.KillSessionWithProcesses(sessionID) + return fmt.Errorf("waiting for deacon to start: %w", err) } // Accept bypass permissions warning dialog if it appears. diff --git a/internal/mayor/manager.go b/internal/mayor/manager.go index eea57071..855cae31 100644 --- a/internal/mayor/manager.go +++ b/internal/mayor/manager.go @@ -114,9 +114,11 @@ func (m *Manager) Start(agentOverride string) error { theme := tmux.MayorTheme() _ = t.ConfigureGasTownSession(sessionID, theme, "", "Mayor", "coordinator") - // Wait for Claude to start (non-fatal) + // Wait for Claude to start - fatal if Claude fails to launch if err := t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil { - // Non-fatal - try to continue anyway + // Kill the zombie session before returning error + _ = t.KillSessionWithProcesses(sessionID) + return fmt.Errorf("waiting for mayor to start: %w", err) } // Accept bypass permissions warning dialog if it appears. diff --git a/internal/refinery/manager.go b/internal/refinery/manager.go index 41d539e8..0534cab4 100644 --- a/internal/refinery/manager.go +++ b/internal/refinery/manager.go @@ -223,10 +223,12 @@ func (m *Manager) Start(foreground bool, agentOverride string) error { return fmt.Errorf("saving state: %w", err) } - // Wait for Claude to start and show its prompt (non-fatal) + // Wait for Claude to start and show its prompt - fatal if Claude fails to launch // WaitForRuntimeReady waits for the runtime to be ready if err := t.WaitForRuntimeReady(sessionID, runtimeConfig, constants.ClaudeStartTimeout); err != nil { - // Non-fatal - try to continue anyway + // Kill the zombie session before returning error + _ = t.KillSessionWithProcesses(sessionID) + return fmt.Errorf("waiting for refinery to start: %w", err) } // Accept bypass permissions warning dialog if it appears. diff --git a/internal/witness/manager.go b/internal/witness/manager.go index bd26933a..78e262a8 100644 --- a/internal/witness/manager.go +++ b/internal/witness/manager.go @@ -211,9 +211,11 @@ func (m *Manager) Start(foreground bool, agentOverride string, envOverrides []st return fmt.Errorf("saving state: %w", err) } - // Wait for Claude to start (non-fatal). + // Wait for Claude to start - fatal if Claude fails to launch if err := t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil { - // Non-fatal - try to continue anyway + // Kill the zombie session before returning error + _ = t.KillSessionWithProcesses(sessionID) + return fmt.Errorf("waiting for witness to start: %w", err) } // Accept bypass permissions warning dialog if it appears.