From f8ef18023990f55b0351522aaf6b9bccf1c2b01a Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 1 Nov 2025 17:05:47 -0700 Subject: [PATCH] Fix bd-e652: Improve bd doctor daemon health checks - Use path normalization (EvalSymlinks) to reliably match daemons across symlinks - Check for stale sockets directly (catches cases where RPC failed) - Detect multiple daemons for same workspace - Enhanced error details for stale daemon detection - Use global daemon registry instead of path-scoped discovery --- cmd/bd/doctor.go | 49 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/cmd/bd/doctor.go b/cmd/bd/doctor.go index e973373c..ba7b2d5f 100644 --- a/cmd/bd/doctor.go +++ b/cmd/bd/doctor.go @@ -637,8 +637,15 @@ func checkMultipleJSONLFiles(path string) doctorCheck { } func checkDaemonStatus(path string) doctorCheck { - // Import daemon discovery from internal package - daemons, err := daemon.DiscoverDaemons([]string{path}) + // Normalize path for reliable comparison (handles symlinks) + wsNorm, err := filepath.EvalSymlinks(path) + if err != nil { + // Fallback to absolute path if EvalSymlinks fails + wsNorm, _ = filepath.Abs(path) + } + + // Use global daemon discovery (registry-based) + daemons, err := daemon.DiscoverDaemons(nil) if err != nil { return doctorCheck{ Name: "Daemon Health", @@ -648,14 +655,35 @@ func checkDaemonStatus(path string) doctorCheck { } } - // Filter to this workspace + // Filter to this workspace using normalized paths var workspaceDaemons []daemon.DaemonInfo for _, d := range daemons { - if d.WorkspacePath == path { + dPath, err := filepath.EvalSymlinks(d.WorkspacePath) + if err != nil { + dPath, _ = filepath.Abs(d.WorkspacePath) + } + if dPath == wsNorm { workspaceDaemons = append(workspaceDaemons, d) } } + // Check for stale socket directly (catches cases where RPC failed so WorkspacePath is empty) + beadsDir := filepath.Join(path, ".beads") + socketPath := filepath.Join(beadsDir, "bd.sock") + if _, err := os.Stat(socketPath); err == nil { + // Socket exists - try to connect + if len(workspaceDaemons) == 0 { + // Socket exists but no daemon found in registry - likely stale + return doctorCheck{ + Name: "Daemon Health", + Status: statusWarning, + Message: "Stale daemon socket detected", + Detail: fmt.Sprintf("Socket exists at %s but daemon is not responding", socketPath), + Fix: "Run 'bd daemons killall' to clean up stale sockets", + } + } + } + if len(workspaceDaemons) == 0 { return doctorCheck{ Name: "Daemon Health", @@ -664,13 +692,24 @@ func checkDaemonStatus(path string) doctorCheck { } } - // Check for version mismatches + // Warn if multiple daemons for same workspace + if len(workspaceDaemons) > 1 { + return doctorCheck{ + Name: "Daemon Health", + Status: statusWarning, + Message: fmt.Sprintf("Multiple daemons detected for this workspace (%d)", len(workspaceDaemons)), + Fix: "Run 'bd daemons killall' to clean up duplicate daemons", + } + } + + // Check for stale or version mismatched daemons for _, d := range workspaceDaemons { if !d.Alive { return doctorCheck{ Name: "Daemon Health", Status: statusWarning, Message: "Stale daemon detected", + Detail: fmt.Sprintf("PID %d is not alive", d.PID), Fix: "Run 'bd daemons killall' to clean up stale daemons", } }