From dbbf338a12d7c9ba2c9619a696d56410eb7f0aa9 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 14 Dec 2025 17:32:54 -0800 Subject: [PATCH] feat: add bd daemon --stop-all to kill all daemon processes (bd-47tn) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cmd/bd/daemon.go | 10 ++++++- cmd/bd/daemon_lifecycle.go | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/cmd/bd/daemon.go b/cmd/bd/daemon.go index 2f2bf203..c6fa35d2 100644 --- a/cmd/bd/daemon.go +++ b/cmd/bd/daemon.go @@ -36,6 +36,7 @@ Common operations: bd daemon --start Start the daemon (background) bd daemon --start --foreground Start in foreground (for systemd/supervisord) 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 --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) { start, _ := cmd.Flags().GetBool("start") stop, _ := cmd.Flags().GetBool("stop") + stopAll, _ := cmd.Flags().GetBool("stop-all") status, _ := cmd.Flags().GetBool("status") health, _ := cmd.Flags().GetBool("health") metrics, _ := cmd.Flags().GetBool("metrics") @@ -54,7 +56,7 @@ Run 'bd daemon' with no flags to see available options.`, foreground, _ := cmd.Flags().GetBool("foreground") // If no operation flags provided, show help - if !start && !stop && !status && !health && !metrics { + if !start && !stop && !stopAll && !status && !health && !metrics { _ = cmd.Help() return } @@ -119,6 +121,11 @@ Run 'bd daemon' with no flags to see available options.`, return } + if stopAll { + stopAllDaemons() + return + } + // If we get here and --start wasn't provided, something is wrong // (should have been caught by help check above) if !start { @@ -222,6 +229,7 @@ func init() { 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("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("health", false, "Check daemon health and metrics") daemonCmd.Flags().Bool("metrics", false, "Show detailed daemon metrics") diff --git a/cmd/bd/daemon_lifecycle.go b/cmd/bd/daemon_lifecycle.go index 9f6be619..b07ddef2 100644 --- a/cmd/bd/daemon_lifecycle.go +++ b/cmd/bd/daemon_lifecycle.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/steveyegge/beads/internal/daemon" "github.com/steveyegge/beads/internal/rpc" ) @@ -275,6 +276,59 @@ func stopDaemon(pidFile string) { 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) func startDaemon(interval time.Duration, autoCommit, autoPush, localMode, foreground bool, logFile, pidFile string) { logPath, err := getLogFilePath(logFile)