refactor: remove bd daemon code from gastown
Remove all code that calls bd daemon commands, as bd daemon functionality has been removed from beads: - Delete internal/beads/daemon.go (CheckBdDaemonHealth, StopAllBdProcesses, etc.) - Delete internal/beads/daemon_test.go - Delete internal/doctor/bd_daemon_check.go (BdDaemonCheck health check) - Remove bd daemon health check from gt status - Remove bd daemon stopping from gt down - Remove bd daemon cleanup from gt install - Remove BdDaemonCheck registration from gt doctor Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,244 +0,0 @@
|
||||
package beads
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
gracefulTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
// BdDaemonInfo represents the status of a single bd daemon instance.
|
||||
type BdDaemonInfo struct {
|
||||
Workspace string `json:"workspace"`
|
||||
SocketPath string `json:"socket_path"`
|
||||
PID int `json:"pid"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Issue string `json:"issue,omitempty"`
|
||||
VersionMismatch bool `json:"version_mismatch,omitempty"`
|
||||
}
|
||||
|
||||
// BdDaemonHealth represents the overall health of bd daemons.
|
||||
type BdDaemonHealth struct {
|
||||
Total int `json:"total"`
|
||||
Healthy int `json:"healthy"`
|
||||
Stale int `json:"stale"`
|
||||
Mismatched int `json:"mismatched"`
|
||||
Unresponsive int `json:"unresponsive"`
|
||||
Daemons []BdDaemonInfo `json:"daemons"`
|
||||
}
|
||||
|
||||
// CheckBdDaemonHealth checks the health of all bd daemons.
|
||||
// Returns nil if no daemons are running (which is fine, bd will use direct mode).
|
||||
func CheckBdDaemonHealth() (*BdDaemonHealth, error) {
|
||||
cmd := exec.Command("bd", "daemon", "health", "--json")
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// bd daemon health may fail if bd not installed or other issues
|
||||
// Return nil to indicate we can't check (not an error for status display)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var health BdDaemonHealth
|
||||
if err := json.Unmarshal(stdout.Bytes(), &health); err != nil {
|
||||
return nil, fmt.Errorf("parsing daemon health: %w", err)
|
||||
}
|
||||
|
||||
return &health, nil
|
||||
}
|
||||
|
||||
// EnsureBdDaemonHealth checks if bd daemons are healthy and attempts to restart if needed.
|
||||
// Returns a warning message if there were issues, or empty string if everything is fine.
|
||||
// This is non-blocking - it will not fail if daemons can't be started.
|
||||
func EnsureBdDaemonHealth(workDir string) string {
|
||||
health, err := CheckBdDaemonHealth()
|
||||
if err != nil || health == nil {
|
||||
// Can't check daemon health - proceed without warning
|
||||
return ""
|
||||
}
|
||||
|
||||
// No daemons running is fine - bd will use direct mode
|
||||
if health.Total == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if any daemons need attention
|
||||
needsRestart := false
|
||||
for _, d := range health.Daemons {
|
||||
switch d.Status {
|
||||
case "healthy":
|
||||
// Good
|
||||
case "version_mismatch", "stale", "unresponsive":
|
||||
needsRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
if !needsRestart {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Attempt to restart daemons
|
||||
if restartErr := restartBdDaemons(); restartErr != nil {
|
||||
return fmt.Sprintf("bd daemons unhealthy (restart failed: %v)", restartErr)
|
||||
}
|
||||
|
||||
// Verify restart worked
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
newHealth, err := CheckBdDaemonHealth()
|
||||
if err != nil || newHealth == nil {
|
||||
return "bd daemons restarted but status unknown"
|
||||
}
|
||||
|
||||
if newHealth.Healthy < newHealth.Total {
|
||||
return fmt.Sprintf("bd daemons partially healthy (%d/%d)", newHealth.Healthy, newHealth.Total)
|
||||
}
|
||||
|
||||
return "" // Successfully restarted
|
||||
}
|
||||
|
||||
// restartBdDaemons restarts all bd daemons.
|
||||
func restartBdDaemons() error { //nolint:unparam // error return kept for future use
|
||||
// Stop all daemons first using pkill to avoid auto-start side effects
|
||||
_ = exec.Command("pkill", "-TERM", "-f", "bd daemon").Run()
|
||||
|
||||
// Give time for cleanup
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Start daemons for known locations
|
||||
// The daemon will auto-start when bd commands are run in those directories
|
||||
// Just running any bd command will trigger daemon startup if configured
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartBdDaemonIfNeeded starts the bd daemon for a specific workspace if not running.
|
||||
// This is a best-effort operation - failures are logged but don't block execution.
|
||||
func StartBdDaemonIfNeeded(workDir string) error {
|
||||
cmd := exec.Command("bd", "daemon", "start")
|
||||
cmd.Dir = workDir
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// StopAllBdProcesses stops all bd daemon and activity processes.
|
||||
// Returns (daemonsKilled, activityKilled, error).
|
||||
// If dryRun is true, returns counts without stopping anything.
|
||||
func StopAllBdProcesses(dryRun, force bool) (int, int, error) {
|
||||
if _, err := exec.LookPath("bd"); err != nil {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
daemonsBefore := CountBdDaemons()
|
||||
activityBefore := CountBdActivityProcesses()
|
||||
|
||||
if dryRun {
|
||||
return daemonsBefore, activityBefore, nil
|
||||
}
|
||||
|
||||
daemonsKilled, daemonsRemaining := stopBdDaemons(force)
|
||||
activityKilled, activityRemaining := stopBdActivityProcesses(force)
|
||||
|
||||
if daemonsRemaining > 0 {
|
||||
return daemonsKilled, activityKilled, fmt.Errorf("bd daemon shutdown incomplete: %d still running", daemonsRemaining)
|
||||
}
|
||||
if activityRemaining > 0 {
|
||||
return daemonsKilled, activityKilled, fmt.Errorf("bd activity shutdown incomplete: %d still running", activityRemaining)
|
||||
}
|
||||
|
||||
return daemonsKilled, activityKilled, nil
|
||||
}
|
||||
|
||||
// CountBdDaemons returns count of running bd daemons.
|
||||
// Uses pgrep instead of "bd daemon list" to avoid triggering daemon auto-start
|
||||
// during shutdown verification.
|
||||
func CountBdDaemons() int {
|
||||
// Use pgrep -f with wc -l for cross-platform compatibility
|
||||
// (macOS pgrep doesn't support -c flag)
|
||||
cmd := exec.Command("sh", "-c", "pgrep -f 'bd daemon' 2>/dev/null | wc -l")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
count, _ := strconv.Atoi(strings.TrimSpace(string(output)))
|
||||
return count
|
||||
}
|
||||
|
||||
|
||||
func stopBdDaemons(force bool) (int, int) {
|
||||
before := CountBdDaemons()
|
||||
if before == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Use pkill directly instead of "bd daemon killall" to avoid triggering
|
||||
// daemon auto-start as a side effect of running bd commands.
|
||||
// Note: pkill -f pattern may match unintended processes in rare cases
|
||||
// (e.g., editors with "bd daemon" in file content). This is acceptable
|
||||
// given the alternative of respawning daemons during shutdown.
|
||||
if force {
|
||||
_ = exec.Command("pkill", "-9", "-f", "bd daemon").Run()
|
||||
} else {
|
||||
_ = exec.Command("pkill", "-TERM", "-f", "bd daemon").Run()
|
||||
time.Sleep(gracefulTimeout)
|
||||
if remaining := CountBdDaemons(); remaining > 0 {
|
||||
_ = exec.Command("pkill", "-9", "-f", "bd daemon").Run()
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
final := CountBdDaemons()
|
||||
killed := before - final
|
||||
if killed < 0 {
|
||||
killed = 0 // Race condition: more processes spawned than we killed
|
||||
}
|
||||
return killed, final
|
||||
}
|
||||
|
||||
// CountBdActivityProcesses returns count of running `bd activity` processes.
|
||||
func CountBdActivityProcesses() int {
|
||||
// Use pgrep -f with wc -l for cross-platform compatibility
|
||||
// (macOS pgrep doesn't support -c flag)
|
||||
cmd := exec.Command("sh", "-c", "pgrep -f 'bd activity' 2>/dev/null | wc -l")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
count, _ := strconv.Atoi(strings.TrimSpace(string(output)))
|
||||
return count
|
||||
}
|
||||
|
||||
func stopBdActivityProcesses(force bool) (int, int) {
|
||||
before := CountBdActivityProcesses()
|
||||
if before == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
if force {
|
||||
_ = exec.Command("pkill", "-9", "-f", "bd activity").Run()
|
||||
} else {
|
||||
_ = exec.Command("pkill", "-TERM", "-f", "bd activity").Run()
|
||||
time.Sleep(gracefulTimeout)
|
||||
if remaining := CountBdActivityProcesses(); remaining > 0 {
|
||||
_ = exec.Command("pkill", "-9", "-f", "bd activity").Run()
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
after := CountBdActivityProcesses()
|
||||
killed := before - after
|
||||
if killed < 0 {
|
||||
killed = 0 // Race condition: more processes spawned than we killed
|
||||
}
|
||||
return killed, after
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package beads
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCountBdActivityProcesses(t *testing.T) {
|
||||
count := CountBdActivityProcesses()
|
||||
if count < 0 {
|
||||
t.Errorf("count should be non-negative, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountBdDaemons(t *testing.T) {
|
||||
if _, err := exec.LookPath("bd"); err != nil {
|
||||
t.Skip("bd not installed")
|
||||
}
|
||||
count := CountBdDaemons()
|
||||
if count < 0 {
|
||||
t.Errorf("count should be non-negative, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopAllBdProcesses_DryRun(t *testing.T) {
|
||||
daemonsKilled, activityKilled, err := StopAllBdProcesses(true, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if daemonsKilled < 0 || activityKilled < 0 {
|
||||
t.Errorf("counts should be non-negative: daemons=%d, activity=%d", daemonsKilled, activityKilled)
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,6 @@ func runDoctor(cmd *cobra.Command, args []string) error {
|
||||
d.Register(doctor.NewCustomTypesCheck())
|
||||
d.Register(doctor.NewRoleLabelCheck())
|
||||
d.Register(doctor.NewFormulaCheck())
|
||||
d.Register(doctor.NewBdDaemonCheck())
|
||||
d.Register(doctor.NewPrefixConflictCheck())
|
||||
d.Register(doctor.NewPrefixMismatchCheck())
|
||||
d.Register(doctor.NewRoutesCheck())
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/daemon"
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
@@ -136,35 +135,7 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Phase 1: Stop bd resurrection layer (--all only)
|
||||
if downAll {
|
||||
daemonsKilled, activityKilled, err := beads.StopAllBdProcesses(downDryRun, downForce)
|
||||
if err != nil {
|
||||
printDownStatus("bd processes", false, err.Error())
|
||||
allOK = false
|
||||
} else {
|
||||
if downDryRun {
|
||||
if daemonsKilled > 0 || activityKilled > 0 {
|
||||
printDownStatus("bd daemon", true, fmt.Sprintf("%d would stop", daemonsKilled))
|
||||
printDownStatus("bd activity", true, fmt.Sprintf("%d would stop", activityKilled))
|
||||
} else {
|
||||
printDownStatus("bd processes", true, "none running")
|
||||
}
|
||||
} else {
|
||||
if daemonsKilled > 0 {
|
||||
printDownStatus("bd daemon", true, fmt.Sprintf("%d stopped", daemonsKilled))
|
||||
}
|
||||
if activityKilled > 0 {
|
||||
printDownStatus("bd activity", true, fmt.Sprintf("%d stopped", activityKilled))
|
||||
}
|
||||
if daemonsKilled == 0 && activityKilled == 0 {
|
||||
printDownStatus("bd processes", true, "none running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2a: Stop refineries
|
||||
// Phase 1: Stop refineries
|
||||
for _, rigName := range rigs {
|
||||
sessionName := fmt.Sprintf("gt-%s-refinery", rigName)
|
||||
if downDryRun {
|
||||
@@ -184,7 +155,7 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2b: Stop witnesses
|
||||
// Phase 2: Stop witnesses
|
||||
for _, rigName := range rigs {
|
||||
sessionName := fmt.Sprintf("gt-%s-witness", rigName)
|
||||
if downDryRun {
|
||||
@@ -428,14 +399,6 @@ func acquireShutdownLock(townRoot string) (*flock.Flock, error) {
|
||||
func verifyShutdown(t *tmux.Tmux, townRoot string) []string {
|
||||
var respawned []string
|
||||
|
||||
if count := beads.CountBdDaemons(); count > 0 {
|
||||
respawned = append(respawned, fmt.Sprintf("bd daemon (%d running)", count))
|
||||
}
|
||||
|
||||
if count := beads.CountBdActivityProcesses(); count > 0 {
|
||||
respawned = append(respawned, fmt.Sprintf("bd activity (%d running)", count))
|
||||
}
|
||||
|
||||
sessions, err := t.ListSessions()
|
||||
if err == nil {
|
||||
for _, sess := range sessions {
|
||||
|
||||
@@ -258,12 +258,6 @@ func runInstall(cmd *cobra.Command, args []string) error {
|
||||
// Town beads (hq- prefix) stores mayor mail, cross-rig coordination, and handoffs.
|
||||
// Rig beads are separate and have their own prefixes.
|
||||
if !installNoBeads {
|
||||
// Kill any orphaned bd daemons before initializing beads.
|
||||
// Stale daemons can interfere with fresh database creation.
|
||||
if killed, _, _ := beads.StopAllBdProcesses(false, true); killed > 0 {
|
||||
fmt.Printf(" ✓ Stopped %d orphaned bd daemon(s)\n", killed)
|
||||
}
|
||||
|
||||
if err := initTownBeads(absPath); err != nil {
|
||||
fmt.Printf(" %s Could not initialize town beads: %v\n", style.Dim.Render("⚠"), err)
|
||||
} else {
|
||||
|
||||
@@ -189,10 +189,6 @@ func runStatusOnce(_ *cobra.Command, _ []string) error {
|
||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
// Check bd daemon health and attempt restart if needed
|
||||
// This is non-blocking - if daemons can't be started, we show a warning but continue
|
||||
bdWarning := beads.EnsureBdDaemonHealth(townRoot)
|
||||
|
||||
// Load town config
|
||||
townConfigPath := constants.MayorTownPath(townRoot)
|
||||
townConfig, err := config.LoadTownConfig(townConfigPath)
|
||||
@@ -404,12 +400,6 @@ func runStatusOnce(_ *cobra.Command, _ []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Show bd daemon warning at the end if there were issues
|
||||
if bdWarning != "" {
|
||||
fmt.Printf("%s %s\n", style.Warning.Render("⚠"), bdWarning)
|
||||
fmt.Printf(" Run 'bd daemon killall && bd daemon start' to restart daemons\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BdDaemonCheck verifies that the bd (beads) daemon is running and healthy.
|
||||
// When the daemon fails to start, it surfaces the actual error (e.g., legacy
|
||||
// database detected, repo mismatch) and provides actionable fix commands.
|
||||
type BdDaemonCheck struct {
|
||||
FixableCheck
|
||||
}
|
||||
|
||||
// NewBdDaemonCheck creates a new bd daemon check.
|
||||
func NewBdDaemonCheck() *BdDaemonCheck {
|
||||
return &BdDaemonCheck{
|
||||
FixableCheck: FixableCheck{
|
||||
BaseCheck: BaseCheck{
|
||||
CheckName: "bd-daemon",
|
||||
CheckDescription: "Check if bd (beads) daemon is running",
|
||||
CheckCategory: CategoryInfrastructure,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run checks if the bd daemon is running and healthy.
|
||||
func (c *BdDaemonCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
// Check daemon status
|
||||
cmd := exec.Command("bd", "daemon", "status")
|
||||
cmd.Dir = ctx.TownRoot
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
output := strings.TrimSpace(stdout.String() + stderr.String())
|
||||
|
||||
// Check if daemon is running
|
||||
if err == nil && strings.Contains(output, "Daemon is running") {
|
||||
// Daemon is running, now check health
|
||||
healthCmd := exec.Command("bd", "daemon", "health")
|
||||
healthCmd.Dir = ctx.TownRoot
|
||||
var healthOut bytes.Buffer
|
||||
healthCmd.Stdout = &healthOut
|
||||
_ = healthCmd.Run() // Ignore error, health check is optional
|
||||
|
||||
healthOutput := healthOut.String()
|
||||
if strings.Contains(healthOutput, "HEALTHY") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusOK,
|
||||
Message: "bd daemon is running and healthy",
|
||||
}
|
||||
}
|
||||
|
||||
// Daemon running but unhealthy
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusWarning,
|
||||
Message: "bd daemon is running but may be unhealthy",
|
||||
Details: []string{strings.TrimSpace(healthOutput)},
|
||||
}
|
||||
}
|
||||
|
||||
// Daemon is not running - try to start it and capture any errors
|
||||
startErr := c.tryStartDaemon(ctx)
|
||||
if startErr != nil {
|
||||
// Parse the error to provide specific guidance
|
||||
return c.parseStartError(startErr)
|
||||
}
|
||||
|
||||
// Started successfully
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusOK,
|
||||
Message: "bd daemon started successfully",
|
||||
}
|
||||
}
|
||||
|
||||
// tryStartDaemon attempts to start the bd daemon and returns any error output.
|
||||
func (c *BdDaemonCheck) tryStartDaemon(ctx *CheckContext) *startError {
|
||||
cmd := exec.Command("bd", "daemon", "start")
|
||||
cmd.Dir = ctx.TownRoot
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &startError{
|
||||
output: strings.TrimSpace(stdout.String() + stderr.String()),
|
||||
exitCode: cmd.ProcessState.ExitCode(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// startError holds information about a failed daemon start.
|
||||
type startError struct {
|
||||
output string
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// parseStartError analyzes the error output and returns a helpful CheckResult.
|
||||
func (c *BdDaemonCheck) parseStartError(err *startError) *CheckResult {
|
||||
output := err.output
|
||||
|
||||
// Check for legacy database error
|
||||
if strings.Contains(output, "LEGACY DATABASE DETECTED") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "bd daemon failed: legacy database detected",
|
||||
Details: []string{
|
||||
"Database was created before bd version 0.17.5",
|
||||
"Missing repository fingerprint prevents daemon from starting",
|
||||
},
|
||||
FixHint: "Run 'bd migrate --update-repo-id' to add fingerprint",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for database mismatch error
|
||||
if strings.Contains(output, "DATABASE MISMATCH DETECTED") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "bd daemon failed: database belongs to different repository",
|
||||
Details: []string{
|
||||
"The .beads database was created for a different git repository",
|
||||
"This can happen if .beads was copied or if the git remote URL changed",
|
||||
},
|
||||
FixHint: "Run 'bd migrate --update-repo-id' if URL changed, or 'rm -rf .beads && bd init' for fresh start",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for already running (not actually an error)
|
||||
if strings.Contains(output, "daemon already running") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusOK,
|
||||
Message: "bd daemon is already running",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for permission/lock errors
|
||||
if strings.Contains(output, "lock") || strings.Contains(output, "permission") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "bd daemon failed: lock or permission issue",
|
||||
Details: []string{output},
|
||||
FixHint: "Check if another bd daemon is running, or remove .beads/daemon.lock",
|
||||
}
|
||||
}
|
||||
|
||||
// Check for database corruption
|
||||
if strings.Contains(output, "corrupt") || strings.Contains(output, "malformed") {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "bd daemon failed: database may be corrupted",
|
||||
Details: []string{output},
|
||||
FixHint: "Run 'bd repair' or 'rm .beads/issues.db && bd sync --from-main'",
|
||||
}
|
||||
}
|
||||
|
||||
// Generic error with full output
|
||||
details := []string{output}
|
||||
if output == "" {
|
||||
details = []string{"No error output captured (exit code " + string(rune('0'+err.exitCode)) + ")"}
|
||||
}
|
||||
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "bd daemon failed to start",
|
||||
Details: details,
|
||||
FixHint: "Check 'bd daemon status' and logs in .beads/daemon.log",
|
||||
}
|
||||
}
|
||||
|
||||
// Fix attempts to start the bd daemon.
|
||||
func (c *BdDaemonCheck) Fix(ctx *CheckContext) error {
|
||||
// First check if it's a legacy database issue
|
||||
startErr := c.tryStartDaemon(ctx)
|
||||
if startErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If legacy database, run migrate first
|
||||
if strings.Contains(startErr.output, "LEGACY DATABASE") ||
|
||||
strings.Contains(startErr.output, "DATABASE MISMATCH") {
|
||||
|
||||
migrateCmd := exec.Command("bd", "migrate", "--update-repo-id", "--yes")
|
||||
migrateCmd.Dir = ctx.TownRoot
|
||||
if err := migrateCmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try starting again
|
||||
startCmd := exec.Command("bd", "daemon", "start")
|
||||
startCmd.Dir = ctx.TownRoot
|
||||
return startCmd.Run()
|
||||
}
|
||||
|
||||
// For other errors, just try to start
|
||||
startCmd := exec.Command("bd", "daemon", "start")
|
||||
startCmd.Dir = ctx.TownRoot
|
||||
return startCmd.Run()
|
||||
}
|
||||
Reference in New Issue
Block a user