feat: add bd daemon --stop-all to kill all daemon processes (bd-47tn)

Add --stop-all flag to bd daemon command that:
- Discovers all running bd daemon processes using the registry
- Gracefully stops them all (with force kill fallback)
- Reports how many were stopped

Useful for:
- Cleaning up multiple daemons causing race conditions
- Getting a clean slate before running bd sync
- Debugging daemon-related issues

🤖 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-14 17:32:54 -08:00
parent ce80664005
commit dbbf338a12
2 changed files with 63 additions and 1 deletions

View File

@@ -36,6 +36,7 @@ Common operations:
bd daemon --start Start the daemon (background) bd daemon --start Start the daemon (background)
bd daemon --start --foreground Start in foreground (for systemd/supervisord) bd daemon --start --foreground Start in foreground (for systemd/supervisord)
bd daemon --stop Stop a running daemon bd daemon --stop Stop a running daemon
bd daemon --stop-all Stop ALL running bd daemons
bd daemon --status Check if daemon is running bd daemon --status Check if daemon is running
bd daemon --health Check daemon health and metrics bd daemon --health Check daemon health and metrics
@@ -43,6 +44,7 @@ Run 'bd daemon' with no flags to see available options.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
start, _ := cmd.Flags().GetBool("start") start, _ := cmd.Flags().GetBool("start")
stop, _ := cmd.Flags().GetBool("stop") stop, _ := cmd.Flags().GetBool("stop")
stopAll, _ := cmd.Flags().GetBool("stop-all")
status, _ := cmd.Flags().GetBool("status") status, _ := cmd.Flags().GetBool("status")
health, _ := cmd.Flags().GetBool("health") health, _ := cmd.Flags().GetBool("health")
metrics, _ := cmd.Flags().GetBool("metrics") metrics, _ := cmd.Flags().GetBool("metrics")
@@ -54,7 +56,7 @@ Run 'bd daemon' with no flags to see available options.`,
foreground, _ := cmd.Flags().GetBool("foreground") foreground, _ := cmd.Flags().GetBool("foreground")
// If no operation flags provided, show help // If no operation flags provided, show help
if !start && !stop && !status && !health && !metrics { if !start && !stop && !stopAll && !status && !health && !metrics {
_ = cmd.Help() _ = cmd.Help()
return return
} }
@@ -119,6 +121,11 @@ Run 'bd daemon' with no flags to see available options.`,
return return
} }
if stopAll {
stopAllDaemons()
return
}
// If we get here and --start wasn't provided, something is wrong // If we get here and --start wasn't provided, something is wrong
// (should have been caught by help check above) // (should have been caught by help check above)
if !start { if !start {
@@ -222,6 +229,7 @@ func init() {
daemonCmd.Flags().Bool("auto-push", false, "Automatically push commits") daemonCmd.Flags().Bool("auto-push", false, "Automatically push commits")
daemonCmd.Flags().Bool("local", false, "Run in local-only mode (no git required, no sync)") daemonCmd.Flags().Bool("local", false, "Run in local-only mode (no git required, no sync)")
daemonCmd.Flags().Bool("stop", false, "Stop running daemon") daemonCmd.Flags().Bool("stop", false, "Stop running daemon")
daemonCmd.Flags().Bool("stop-all", false, "Stop all running bd daemons")
daemonCmd.Flags().Bool("status", false, "Show daemon status") daemonCmd.Flags().Bool("status", false, "Show daemon status")
daemonCmd.Flags().Bool("health", false, "Check daemon health and metrics") daemonCmd.Flags().Bool("health", false, "Check daemon health and metrics")
daemonCmd.Flags().Bool("metrics", false, "Show detailed daemon metrics") daemonCmd.Flags().Bool("metrics", false, "Show detailed daemon metrics")

View File

@@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/steveyegge/beads/internal/daemon"
"github.com/steveyegge/beads/internal/rpc" "github.com/steveyegge/beads/internal/rpc"
) )
@@ -275,6 +276,59 @@ func stopDaemon(pidFile string) {
fmt.Println("Daemon killed") fmt.Println("Daemon killed")
} }
// stopAllDaemons stops all running bd daemons (bd-47tn)
func stopAllDaemons() {
// Discover all running daemons using the registry
daemons, err := daemon.DiscoverDaemons(nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error discovering daemons: %v\n", err)
os.Exit(1)
}
// Filter to only alive daemons
var alive []daemon.DaemonInfo
for _, d := range daemons {
if d.Alive {
alive = append(alive, d)
}
}
if len(alive) == 0 {
if jsonOutput {
fmt.Println(`{"stopped": 0, "message": "No running daemons found"}`)
} else {
fmt.Println("No running daemons found")
}
return
}
if !jsonOutput {
fmt.Printf("Found %d running daemon(s), stopping...\n", len(alive))
}
// Stop all daemons (with force=true for stubborn processes)
results := daemon.KillAllDaemons(alive, true)
if jsonOutput {
output, _ := json.MarshalIndent(results, "", " ")
fmt.Println(string(output))
} else {
if results.Stopped > 0 {
fmt.Printf("✓ Stopped %d daemon(s)\n", results.Stopped)
}
if results.Failed > 0 {
fmt.Printf("✗ Failed to stop %d daemon(s):\n", results.Failed)
for _, f := range results.Failures {
fmt.Printf(" - PID %d (%s): %s\n", f.PID, f.Workspace, f.Error)
}
}
}
if results.Failed > 0 {
os.Exit(1)
}
}
// startDaemon starts the daemon (in foreground if requested, otherwise background) // startDaemon starts the daemon (in foreground if requested, otherwise background)
func startDaemon(interval time.Duration, autoCommit, autoPush, localMode, foreground bool, logFile, pidFile string) { func startDaemon(interval time.Duration, autoCommit, autoPush, localMode, foreground bool, logFile, pidFile string) {
logPath, err := getLogFilePath(logFile) logPath, err := getLogFilePath(logFile)