Fix daemon shutdown detection bug

## Problem
gt shutdown failed to stop orphaned daemon processes because the
detection mechanism ignored errors and had no fallback.

## Root Cause
stopDaemonIfRunning() ignored errors from daemon.IsRunning(), causing:
1. Stale PID files to hide running daemons
2. Corrupted PID files to return silent false
3. No fallback detection for orphaned processes
4. Early return when no sessions running prevented daemon check

## Solution
1. Enhanced IsRunning() to return detailed errors
2. Added process name verification (prevents PID reuse false positives)
3. Added fallback orphan detection using pgrep
4. Fixed stopDaemonIfRunning() to handle errors and use fallback
5. Added daemon check even when no sessions are running

## Testing
Verified shutdown now:
- Detects and reports stale/corrupted PID files
- Finds orphaned daemon processes
- Kills all daemon processes reliably
- Reports detailed status during shutdown
- Works even when no other sessions are running

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Roland Tritsch
2026-01-18 07:51:48 +00:00
committed by Steve Yegge
parent 2aadb0165b
commit 6bfe61f796
2 changed files with 152 additions and 11 deletions

View File

@@ -450,6 +450,14 @@ func runShutdown(cmd *cobra.Command, args []string) error {
if len(toStop) == 0 {
fmt.Printf("%s Gas Town was not running\n", style.Dim.Render("○"))
// Still check for orphaned daemons even if no sessions are running
if townRoot != "" {
fmt.Println()
fmt.Println("Checking for orphaned daemon...")
stopDaemonIfRunning(townRoot)
}
return nil
}
@@ -797,16 +805,48 @@ func cleanupPolecats(townRoot string) {
// stopDaemonIfRunning stops the daemon if it is running.
// This prevents the daemon from restarting agents after shutdown.
// Uses robust detection with fallback to process search.
func stopDaemonIfRunning(townRoot string) {
running, _, _ := daemon.IsRunning(townRoot)
// Primary detection: PID file
running, pid, err := daemon.IsRunning(townRoot)
if err != nil {
// Detection error - report it but continue with fallback
fmt.Printf(" %s Daemon detection warning: %s\n", style.Bold.Render("⚠"), err.Error())
}
if running {
// PID file points to live daemon - stop it
if err := daemon.StopDaemon(townRoot); err != nil {
fmt.Printf(" %s Daemon: %s\n", style.Dim.Render("○"), err.Error())
fmt.Printf(" %s Failed to stop daemon (PID %d): %s\n",
style.Bold.Render("✗"), pid, err.Error())
} else {
fmt.Printf(" %s Daemon stopped\n", style.Bold.Render("✓"))
fmt.Printf(" %s Daemon stopped (was PID %d)\n", style.Bold.Render("✓"), pid)
}
} else {
fmt.Printf(" %s Daemon not running\n", style.Dim.Render("○"))
fmt.Printf(" %s Daemon not tracked by PID file\n", style.Dim.Render("○"))
}
// Fallback: Search for orphaned daemon processes
orphaned, err := daemon.FindOrphanedDaemons()
if err != nil {
fmt.Printf(" %s Warning: failed to search for orphaned daemons: %v\n",
style.Dim.Render("○"), err)
return
}
if len(orphaned) > 0 {
fmt.Printf(" %s Found %d orphaned daemon process(es): %v\n",
style.Bold.Render("⚠"), len(orphaned), orphaned)
killed, err := daemon.KillOrphanedDaemons()
if err != nil {
fmt.Printf(" %s Failed to kill orphaned daemons: %v\n",
style.Bold.Render("✗"), err)
} else if killed > 0 {
fmt.Printf(" %s Killed %d orphaned daemon(s)\n",
style.Bold.Render("✓"), killed)
}
}
}