fix(daemon): allow read-only daemon commands with Dolt backend

The daemon guard was blocking ALL daemon commands when using Dolt
backend, including read-only commands like `status`, `stop`, `logs`.

Changes:
- Rename guard to `guardDaemonStartForDolt` (more accurate)
- Remove `PersistentPreRunE` from `daemonCmd` and `daemonsCmd`
- Add `PreRunE` guard only to `daemonStartCmd` and `daemonsRestartCmd`
- Update test to use new function name and test start command

Now:
- `bd daemon status` works with Dolt backend
- `bd daemon start` blocked unless `--federation` flag
- `bd daemon start --federation` works (starts dolt sql-server)

Fixes: bd-n7o47

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/emma
2026-01-21 18:05:53 -08:00
committed by Steve Yegge
parent 4e3e9d1441
commit bb4549abdd
5 changed files with 17 additions and 11 deletions

View File

@@ -48,7 +48,6 @@ Common operations:
bd daemon killall Stop all running daemons bd daemon killall Stop all running daemons
Run 'bd daemon --help' to see all subcommands.`, Run 'bd daemon --help' to see all subcommands.`,
PersistentPreRunE: guardDaemonUnsupportedForDolt,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
start, _ := cmd.Flags().GetBool("start") start, _ := cmd.Flags().GetBool("start")
stop, _ := cmd.Flags().GetBool("stop") stop, _ := cmd.Flags().GetBool("stop")

View File

@@ -19,8 +19,8 @@ func singleProcessBackendHelp(backend string) string {
return fmt.Sprintf("daemon mode is not supported with the %q backend (single-process only). To use daemon mode, initialize with %q (e.g. `bd init --backend sqlite`). Otherwise run commands in direct mode (default for dolt)", b, configfile.BackendSQLite) return fmt.Sprintf("daemon mode is not supported with the %q backend (single-process only). To use daemon mode, initialize with %q (e.g. `bd init --backend sqlite`). Otherwise run commands in direct mode (default for dolt)", b, configfile.BackendSQLite)
} }
// guardDaemonUnsupportedForDolt blocks all daemon-related commands when the current // guardDaemonStartForDolt blocks daemon start/restart commands when the current
// workspace backend is Dolt. // workspace backend is Dolt, unless --federation is specified.
// //
// Rationale: embedded Dolt is effectively single-writer at the OS-process level. The // Rationale: embedded Dolt is effectively single-writer at the OS-process level. The
// daemon architecture relies on multiple processes (CLI + daemon + helper spawns), // daemon architecture relies on multiple processes (CLI + daemon + helper spawns),
@@ -28,8 +28,12 @@ func singleProcessBackendHelp(backend string) string {
// //
// Exception: --federation flag enables dolt sql-server mode which is multi-writer. // Exception: --federation flag enables dolt sql-server mode which is multi-writer.
// //
// Note: This guard should only be attached to commands that START a daemon process
// (start, restart). Read-only commands (status, stop, logs, health, list) are allowed
// even with Dolt backend.
//
// We still allow help output so users can discover the command surface. // We still allow help output so users can discover the command surface.
func guardDaemonUnsupportedForDolt(cmd *cobra.Command, _ []string) error { func guardDaemonStartForDolt(cmd *cobra.Command, _ []string) error {
// Allow `--help` for any daemon subcommand. // Allow `--help` for any daemon subcommand.
if helpFlag := cmd.Flags().Lookup("help"); helpFlag != nil { if helpFlag := cmd.Flags().Lookup("help"); helpFlag != nil {
if help, _ := cmd.Flags().GetBool("help"); help { if help, _ := cmd.Flags().GetBool("help"); help {

View File

@@ -35,6 +35,7 @@ Examples:
bd daemon start --foreground # Run in foreground (for systemd/supervisord) bd daemon start --foreground # Run in foreground (for systemd/supervisord)
bd daemon start --local # Local-only mode (no git sync) bd daemon start --local # Local-only mode (no git sync)
bd daemon start --federation # Enable federation mode (dolt sql-server)`, bd daemon start --federation # Enable federation mode (dolt sql-server)`,
PreRunE: guardDaemonStartForDolt,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
interval, _ := cmd.Flags().GetDuration("interval") interval, _ := cmd.Flags().GetDuration("interval")
autoCommit, _ := cmd.Flags().GetBool("auto-commit") autoCommit, _ := cmd.Flags().GetBool("auto-commit")

View File

@@ -79,7 +79,6 @@ Subcommands:
logs - View daemon logs logs - View daemon logs
killall - Stop all running daemons killall - Stop all running daemons
restart - Restart a specific daemon (not yet implemented)`, restart - Restart a specific daemon (not yet implemented)`,
PersistentPreRunE: guardDaemonUnsupportedForDolt,
} }
var daemonsListCmd = &cobra.Command{ var daemonsListCmd = &cobra.Command{
Use: "list", Use: "list",
@@ -225,7 +224,8 @@ var daemonsRestartCmd = &cobra.Command{
Short: "Restart a specific bd daemon", Short: "Restart a specific bd daemon",
Long: `Restart a specific bd daemon by workspace path or PID. Long: `Restart a specific bd daemon by workspace path or PID.
Stops the daemon gracefully, then starts a new one.`, Stops the daemon gracefully, then starts a new one.`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
PreRunE: guardDaemonStartForDolt,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
target := args[0] target := args[0]
searchRoots, _ := cmd.Flags().GetStringSlice("search") searchRoots, _ := cmd.Flags().GetStringSlice("search")

View File

@@ -69,7 +69,7 @@ func TestDoltSingleProcess_TryAutoStartDoesNotCreateStartlock(t *testing.T) {
} }
} }
func TestDoltSingleProcess_DaemonGuardBlocksCommands(t *testing.T) { func TestDoltSingleProcess_DaemonGuardBlocksStartCommand(t *testing.T) {
oldDBPath := dbPath oldDBPath := dbPath
t.Cleanup(func() { dbPath = oldDBPath }) t.Cleanup(func() { dbPath = oldDBPath })
dbPath = "" dbPath = ""
@@ -78,12 +78,14 @@ func TestDoltSingleProcess_DaemonGuardBlocksCommands(t *testing.T) {
beadsDir, _ := writeDoltWorkspace(t, ws) beadsDir, _ := writeDoltWorkspace(t, ws)
t.Setenv("BEADS_DIR", beadsDir) t.Setenv("BEADS_DIR", beadsDir)
// Ensure help flag exists (cobra adds it during execution; for unit testing we add it explicitly). // Use daemonStartCmd which has the guard attached.
cmd := daemonCmd // Ensure help and federation flags exist (cobra adds them during execution).
cmd := daemonStartCmd
cmd.Flags().Bool("help", false, "help") cmd.Flags().Bool("help", false, "help")
err := guardDaemonUnsupportedForDolt(cmd, nil) // Note: federation flag is already registered in init()
err := guardDaemonStartForDolt(cmd, nil)
if err == nil { if err == nil {
t.Fatalf("expected daemon guard error for dolt backend") t.Fatalf("expected daemon guard error for dolt backend without --federation")
} }
if !strings.Contains(err.Error(), "single-process") { if !strings.Contains(err.Error(), "single-process") {
t.Fatalf("expected error to mention single-process, got: %v", err) t.Fatalf("expected error to mention single-process, got: %v", err)