From 2f96795f8527be974719826a0b8d45b638015993 Mon Sep 17 00:00:00 2001 From: mayor Date: Fri, 2 Jan 2026 16:06:09 -0800 Subject: [PATCH] fix(daemon): propagate startup failure reason to user (GH#863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When daemon fails to start due to legacy database or fingerprint validation, the error was only logged to daemon.log. Users saw "Daemon took too long" with no hint about the actual problem. Changes: - Write validation errors to .beads/daemon-error file before daemon exits - Check for daemon-error file in autostart and display contents on timeout - Elevate legacy database check in bd doctor from warning to error Now when daemon fails due to legacy database, users see: "LEGACY DATABASE DETECTED! ... Run 'bd migrate --update-repo-id' to add fingerprint" Instead of just "Daemon took too long to start". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/daemon.go | 6 ++++++ cmd/bd/daemon_autostart.go | 11 +++++++++++ cmd/bd/doctor/integrity.go | 12 ++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/cmd/bd/daemon.go b/cmd/bd/daemon.go index cb6baaa0..13ea819b 100644 --- a/cmd/bd/daemon.go +++ b/cmd/bd/daemon.go @@ -452,6 +452,12 @@ func runDaemonLoop(interval time.Duration, autoCommit, autoPush, autoPull, local } else if err := validateDatabaseFingerprint(ctx, store, &log); err != nil { if os.Getenv("BEADS_IGNORE_REPO_MISMATCH") != "1" { log.Error("repository fingerprint validation failed", "error", err) + // Write error to daemon-error file so user sees it instead of just "daemon took too long" + errFile := filepath.Join(beadsDir, "daemon-error") + // nolint:gosec // G306: Error file needs to be readable for debugging + if writeErr := os.WriteFile(errFile, []byte(err.Error()), 0644); writeErr != nil { + log.Warn("could not write daemon-error file", "error", writeErr) + } return // Use return instead of os.Exit to allow defers to run } log.Warn("repository mismatch ignored (BEADS_IGNORE_REPO_MISMATCH=1)") diff --git a/cmd/bd/daemon_autostart.go b/cmd/bd/daemon_autostart.go index 97419154..c4afc0d4 100644 --- a/cmd/bd/daemon_autostart.go +++ b/cmd/bd/daemon_autostart.go @@ -340,6 +340,17 @@ func startDaemonProcess(socketPath string) bool { recordDaemonStartFailure() debugLog("daemon socket not ready after 5 seconds") + + // Check for daemon-error file which contains the actual failure reason + beadsDir := filepath.Dir(dbPath) + errFile := filepath.Join(beadsDir, "daemon-error") + if errContent, err := os.ReadFile(errFile); err == nil && len(errContent) > 0 { + // Show the actual error from the daemon + fmt.Fprintf(os.Stderr, "%s Daemon failed to start:\n", ui.RenderWarn("Warning:")) + fmt.Fprintf(os.Stderr, "%s\n", string(errContent)) + return false + } + // Emit visible warning so user understands why command was slow fmt.Fprintf(os.Stderr, "%s Daemon took too long to start (>5s). Running in direct mode.\n", ui.RenderWarn("Warning:")) fmt.Fprintf(os.Stderr, " %s Run 'bd doctor' to diagnose daemon issues\n", ui.RenderMuted("Hint:")) diff --git a/cmd/bd/doctor/integrity.go b/cmd/bd/doctor/integrity.go index 07cf4bc8..646cd93e 100644 --- a/cmd/bd/doctor/integrity.go +++ b/cmd/bd/doctor/integrity.go @@ -441,12 +441,12 @@ func CheckRepoFingerprint(path string) DoctorCheck { err = db.QueryRow("SELECT value FROM metadata WHERE key = 'repo_id'").Scan(&storedRepoID) if err != nil { if err == sql.ErrNoRows || strings.Contains(err.Error(), "no such table") { - // Legacy database without repo_id + // Legacy database without repo_id - this is an error because daemon won't start return DoctorCheck{ Name: "Repo Fingerprint", - Status: StatusWarning, + Status: StatusError, Message: "Legacy database (no fingerprint)", - Detail: "Database was created before version 0.17.5", + Detail: "Database was created before version 0.17.5. Daemon will fail to start.", Fix: "Run 'bd migrate --update-repo-id' to add fingerprint", } } @@ -458,13 +458,13 @@ func CheckRepoFingerprint(path string) DoctorCheck { } } - // If repo_id is empty, treat as legacy + // If repo_id is empty, treat as legacy - this is an error because daemon won't start if storedRepoID == "" { return DoctorCheck{ Name: "Repo Fingerprint", - Status: StatusWarning, + Status: StatusError, Message: "Legacy database (empty fingerprint)", - Detail: "Database was created before version 0.17.5", + Detail: "Database was created before version 0.17.5. Daemon will fail to start.", Fix: "Run 'bd migrate --update-repo-id' to add fingerprint", } }