diff --git a/internal/boot/boot.go b/internal/boot/boot.go index 52f22c84..2b1a42f0 100644 --- a/internal/boot/boot.go +++ b/internal/boot/boot.go @@ -197,6 +197,11 @@ func (b *Boot) spawnTmux() error { // Launch Claude with environment exported inline and initial triage prompt // The "gt boot triage" prompt tells Boot to immediately start triage (GUPP principle) startCmd := config.BuildAgentStartupCommand("boot", "deacon-boot", "", "gt boot triage") + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := b.tmux.WaitForShellReady(SessionName, 5*time.Second); err != nil { + _ = b.tmux.KillSession(SessionName) + return fmt.Errorf("waiting for shell: %w", err) + } if err := b.tmux.SendKeys(SessionName, startCmd); err != nil { return fmt.Errorf("sending startup command: %w", err) } diff --git a/internal/deacon/manager.go b/internal/deacon/manager.go index 8d0f2262..43fda759 100644 --- a/internal/deacon/manager.go +++ b/internal/deacon/manager.go @@ -99,6 +99,11 @@ func (m *Manager) Start() error { runtimeCmd, ) + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil { + _ = t.KillSession(sessionID) + return fmt.Errorf("waiting for shell: %w", err) + } if err := t.SendKeysDelayed(sessionID, respawnCmd, 200); err != nil { _ = t.KillSession(sessionID) // best-effort cleanup return fmt.Errorf("starting Claude agent: %w", err) diff --git a/internal/mayor/manager.go b/internal/mayor/manager.go index 62b74085..3a6a985b 100644 --- a/internal/mayor/manager.go +++ b/internal/mayor/manager.go @@ -98,6 +98,11 @@ func (m *Manager) Start(agentOverride string) error { _ = t.KillSession(sessionID) // best-effort cleanup return fmt.Errorf("building startup command: %w", err) } + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil { + _ = t.KillSession(sessionID) + return fmt.Errorf("waiting for shell: %w", err) + } if err := t.SendKeysDelayed(sessionID, startupCmd, 200); err != nil { _ = t.KillSession(sessionID) // best-effort cleanup return fmt.Errorf("starting Claude agent: %w", err) diff --git a/internal/polecat/session_manager.go b/internal/polecat/session_manager.go index e24d8537..bc4e86a5 100644 --- a/internal/polecat/session_manager.go +++ b/internal/polecat/session_manager.go @@ -183,6 +183,11 @@ func (m *SessionManager) Start(polecat string, opts SessionStartOptions) error { if command == "" { command = config.BuildPolecatStartupCommand(m.rig.Name, polecat, m.rig.Path, "") } + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := m.tmux.WaitForShellReady(sessionID, 5*time.Second); err != nil { + _ = m.tmux.KillSession(sessionID) + return fmt.Errorf("waiting for shell: %w", err) + } if err := m.tmux.SendKeys(sessionID, command); err != nil { return fmt.Errorf("sending command: %w", err) } diff --git a/internal/refinery/manager.go b/internal/refinery/manager.go index 75302161..3f2262b4 100644 --- a/internal/refinery/manager.go +++ b/internal/refinery/manager.go @@ -210,6 +210,11 @@ func (m *Manager) Start(foreground bool) error { // Restarts are handled by daemon via LIFECYCLE mail, not shell loops // Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes command := config.BuildAgentStartupCommand("refinery", bdActor, m.rig.Path, "") + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil { + _ = t.KillSession(sessionID) + return fmt.Errorf("waiting for shell: %w", err) + } if err := t.SendKeys(sessionID, command); err != nil { // Clean up the session on failure (best-effort cleanup) _ = t.KillSession(sessionID) diff --git a/internal/witness/manager.go b/internal/witness/manager.go index 3d67eff1..884048e8 100644 --- a/internal/witness/manager.go +++ b/internal/witness/manager.go @@ -183,6 +183,11 @@ func (m *Manager) Start(foreground bool) error { // Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes // Pass m.rig.Path so rig agent settings are honored (not town-level defaults) command := config.BuildAgentStartupCommand("witness", bdActor, m.rig.Path, "") + // Wait for shell to be ready before sending keys (prevents "can't find pane" under load) + if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil { + _ = t.KillSession(sessionID) + return fmt.Errorf("waiting for shell: %w", err) + } if err := t.SendKeys(sessionID, command); err != nil { _ = t.KillSession(sessionID) // best-effort cleanup return fmt.Errorf("starting Claude agent: %w", err)