Add daemon health check endpoint (bd-146)

- Add OpHealth RPC operation to protocol
- Implement handleHealth() with DB ping and 1s timeout
- Returns status (healthy/degraded/unhealthy), uptime, cache metrics
- Update TryConnect() to use health check instead of ping
- Add 'bd daemon --health' CLI command with JSON output
- Track cache hits/misses for metrics
- Unhealthy daemon triggers automatic fallback to direct mode
- Health check completes in <2 seconds

Amp-Thread-ID: https://ampcode.com/threads/T-1a4889f3-77cf-433a-a704-e1c383929f48
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-18 13:41:06 -07:00
parent f987722f96
commit 9e2ee1889f
6 changed files with 199 additions and 5 deletions

View File

@@ -17,7 +17,7 @@ type Client struct {
}
// TryConnect attempts to connect to the daemon socket
// Returns nil if no daemon is running
// Returns nil if no daemon is running or unhealthy
func TryConnect(socketPath string) (*Client, error) {
if _, err := os.Stat(socketPath); os.IsNotExist(err) {
if os.Getenv("BD_DEBUG") != "" {
@@ -40,14 +40,28 @@ func TryConnect(socketPath string) (*Client, error) {
timeout: 30 * time.Second,
}
if err := client.Ping(); err != nil {
health, err := client.Health()
if err != nil {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: ping failed: %v\n", err)
fmt.Fprintf(os.Stderr, "Debug: health check failed: %v\n", err)
}
conn.Close()
return nil, nil
}
if health.Status == "unhealthy" {
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: daemon unhealthy: %s\n", health.Error)
}
conn.Close()
return nil, nil
}
if os.Getenv("BD_DEBUG") != "" {
fmt.Fprintf(os.Stderr, "Debug: connected to daemon (status: %s, uptime: %.1fs, cache: %d)\n",
health.Status, health.Uptime, health.CacheSize)
}
return client, nil
}
@@ -131,6 +145,21 @@ func (c *Client) Ping() error {
return nil
}
// Health sends a health check request to verify the daemon is healthy
func (c *Client) Health() (*HealthResponse, error) {
resp, err := c.Execute(OpHealth, nil)
if err != nil {
return nil, err
}
var health HealthResponse
if err := json.Unmarshal(resp.Data, &health); err != nil {
return nil, fmt.Errorf("failed to unmarshal health response: %w", err)
}
return &health, nil
}
// Create creates a new issue via the daemon
func (c *Client) Create(args *CreateArgs) (*Response, error) {
return c.Execute(OpCreate, args)