refactor: remove shell respawn loops from witness and deacon (gt-zxgu)

Shell loops bypassed the proper lifecycle architecture. Now:

- Witness: Launches Claude directly, daemon/deacon health-scan handles restart
- Deacon: Launches Claude directly, daemon detects exit and restarts
- Daemon: ensureDeaconRunning() now checks if Claude is running (pane cmd)
         and restarts it if session exists but Claude has exited
- gt deacon restart: Now does stop+start instead of Ctrl-C

This enforces proper lifecycle flow through LIFECYCLE mail and daemon
heartbeat rather than bypassing it with shell loops.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-23 04:02:17 -08:00
parent 46a5e38fa2
commit 5f2e16f789
3 changed files with 47 additions and 22 deletions

View File

@@ -153,11 +153,10 @@ func startDeaconSession(t *tmux.Tmux) error {
theme := tmux.DeaconTheme()
_ = t.ConfigureGasTownSession(DeaconSessionName, theme, "", "Deacon", "health-check")
// Launch Claude in a respawn loop - session survives restarts
// Launch Claude directly (no shell respawn loop)
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
// The startup hook handles context loading automatically
// Use SendKeysDelayed to allow shell initialization after NewSession
loopCmd := `while true; do echo "⛪ Starting Deacon session..."; claude --dangerously-skip-permissions; echo ""; echo "Deacon exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`
if err := t.SendKeysDelayed(DeaconSessionName, loopCmd, 200); err != nil {
if err := t.SendKeys(DeaconSessionName, "claude --dangerously-skip-permissions"); err != nil {
return fmt.Errorf("sending command: %w", err)
}
@@ -257,16 +256,23 @@ func runDeaconRestart(cmd *cobra.Command, args []string) error {
return fmt.Errorf("checking session: %w", err)
}
fmt.Println("Restarting Deacon...")
if running {
// Graceful restart: send Ctrl-C to exit Claude, loop will restart it
fmt.Println("Restarting Deacon (sending Ctrl-C to trigger respawn loop)...")
_ = t.SendKeysRaw(DeaconSessionName, "C-c")
fmt.Printf("%s Deacon will restart automatically. Session stays attached.\n", style.Bold.Render("✓"))
return nil
// Kill existing session
if err := t.KillSession(DeaconSessionName); err != nil {
fmt.Printf("%s Warning: failed to kill session: %v\n", style.Dim.Render("⚠"), err)
}
}
// Not running, start fresh
return runDeaconStart(cmd, args)
// Start fresh
if err := runDeaconStart(cmd, args); err != nil {
return err
}
fmt.Printf("%s Deacon restarted\n", style.Bold.Render("✓"))
fmt.Printf(" %s\n", style.Dim.Render("Use 'gt deacon attach' to connect"))
return nil
}
func runDeaconHeartbeat(cmd *cobra.Command, args []string) error {