Files
gastown/internal/beads/daemon.go
max 1b69576573 fix: Address golangci-lint errors (errcheck, gosec) (#76)
Apply PR #76 from dannomayernotabot:

- Add golangci exclusions for internal package false positives
- Tighten file permissions (0644 -> 0600) for sensitive files
- Add ReadHeaderTimeout to HTTP server (slowloris prevention)
- Explicit error ignoring with _ = for intentional cases
- Add //nolint comments with justifications
- Spelling: cancelled -> canceled (US locale)

Co-Authored-By: dannomayernotabot <noreply@github.com>

🤖 Generated with Claude Code
2026-01-03 16:11:55 -08:00

135 lines
4.0 KiB
Go

package beads
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"time"
)
// 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
var issues []string
for _, d := range health.Daemons {
switch d.Status {
case "healthy":
// Good
case "version_mismatch":
needsRestart = true
issues = append(issues, fmt.Sprintf("%s: version mismatch", d.Workspace))
case "stale":
needsRestart = true
issues = append(issues, fmt.Sprintf("%s: stale", d.Workspace))
case "unresponsive":
needsRestart = true
issues = append(issues, fmt.Sprintf("%s: unresponsive", d.Workspace))
}
}
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
stopCmd := exec.Command("bd", "daemon", "killall")
_ = stopCmd.Run() // Ignore errors - daemons might not be running
// 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()
}