Skip to content

Conversation

@Harrison97
Copy link

Summary

Make bd daemon start idempotent - when a compatible daemon is already running, exit successfully (code 0) instead of failing (code 1).

Problem

During gt reload, there's a race condition where something auto-starts the bd daemon between the killall and start phases. This causes "already running" errors even though the reload should succeed.

The root cause is that bd auto-starts daemons when running any command. A hook, background task, or any bd command can trigger this.

Solution

If the daemon is already running AND healthy AND version-compatible, print a message and exit 0 instead of exit 1. This makes the operation idempotent.

$ bd daemon start
Starting bd daemon...
$ bd daemon start  
Daemon already running (PID 12345, version 0.49.0)  # exit 0, not exit 1

Test plan

  • Build bd with fix
  • Verify bd daemon start returns exit 0 when daemon already running
  • Verify gt reload completes successfully without race condition errors

Fixes: #1331

🤖 Generated with Claude Code

Harrison97 and others added 2 commits January 25, 2026 23:52
Add resolveExternalDependencies() function that queries raw dependency
records, finds external refs (external:project:id format), and resolves
them via routing to fetch actual issue metadata from target rigs.

This fixes cross-rig convoy tracking where bd dep list would return
empty results for issues stored as external references.

Fixes: steveyegge#1332

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When `bd daemon start` is called and a compatible daemon is already
running, exit successfully (code 0) instead of failing (code 1).

This fixes race conditions during `gt reload` where something auto-starts
the bd daemon between the killall and start phases, causing "already
running" errors.

The fix is simple: if daemon is running AND healthy AND version-compatible,
print a message and exit 0. Previously this was treated as an error.

Fixes: steveyegge#1331

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@citadelgrad citadelgrad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! One issue found during review.

What's Good

  • ✅ The daemon idempotency change is correct and minimal
  • ✅ Good verbose logging in resolveExternalDependencies for debugging
  • ✅ Graceful failure - returns nil on errors so local deps still work
  • ✅ Proper resource cleanup (closes DB connections)

Hopefully not too many notifications. I was experimenting with gh cli and making mistakes.


// Open storage for the target rig
targetDBPath := filepath.Join(targetBeadsDir, "beads.db")
targetStore, err := sqlite.New(ctx, targetDBPath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses wrong database initialization pattern

The PR uses raw sqlite.New() but other cross-rig commands use factory.NewFromConfig() which respects backend configuration:

// Current (inconsistent):
targetDBPath := filepath.Join(targetBeadsDir, "beads.db")
targetStore, err := sqlite.New(ctx, targetDBPath)

// Established pattern (from create.go:947, move.go, refile.go):
targetStore, err := factory.NewFromConfig(ctx, targetBeadsDir)

Using factory.NewFromConfig ensures correct backend selection and consistent behavior with other cross-rig operations.

var result []*types.IssueWithDependencyMetadata
beadsDir := getBeadsDir()

for _, dep := range deps {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance consideration in resolveExternalDependencies

The function opens a new database connection for each external dependency. If multiple deps reference the same project, this is inefficient:

  // Current: opens/closes DB per dependency
  for _, dep := range deps {
      targetStore, err := sqlite.New(ctx, targetDBPath)  // Opens here
      issue, err := targetStore.GetIssue(ctx, targetID)
      _ = targetStore.Close()  // Closes here
  }

Consider grouping by project first, then batch-fetching:

// Group deps by project, open each DB once
  depsByProject := groupByProject(deps)
  for project, projectDeps := range depsByProject {
      targetStore := openOnce(project)
      defer targetStore.Close()
      for _, dep := range projectDeps {
          // fetch issues
      }
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bd daemon auto-starts causing reload/restart failures

2 participants