From 6572654cdc1ba54e1f192edbf7f2f40bf3e212b8 Mon Sep 17 00:00:00 2001 From: beads/crew/fang Date: Fri, 2 Jan 2026 00:01:58 -0800 Subject: [PATCH] fix: allow yaml-only config operations without database (GH#536) Users could not run 'bd config set no-db true' without already having a database, creating a chicken-and-egg problem. The PersistentPreRunE would fail with 'no beads database found' before the config command could even run. The fix detects when a yaml-only config operation is being attempted (config set/get with keys like no-db, no-daemon, sync.branch, etc.) and allows it to proceed without requiring a database. Before: $ bd config set no-db true Error: no beads database found After: $ bd config set no-db true Set no-db = true (in config.yaml) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/config_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++ cmd/bd/main.go | 9 ++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/cmd/bd/config_test.go b/cmd/bd/config_test.go index 324948d9..7a753f81 100644 --- a/cmd/bd/config_test.go +++ b/cmd/bd/config_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/steveyegge/beads/internal/config" "github.com/steveyegge/beads/internal/storage/sqlite" ) @@ -148,6 +149,52 @@ func TestConfigNamespaces(t *testing.T) { } } +// TestYamlOnlyConfigWithoutDatabase verifies that yaml-only config keys +// (like no-db, no-daemon) can be set/get without requiring a SQLite database. +// This is the fix for GH#536 - the chicken-and-egg problem where you couldn't +// run `bd config set no-db true` without first having a database. +func TestYamlOnlyConfigWithoutDatabase(t *testing.T) { + // Create a temp directory with only config.yaml (no database) + tmpDir, err := os.MkdirTemp("", "bd-test-yaml-config-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + beadsDir := filepath.Join(tmpDir, ".beads") + if err := os.MkdirAll(beadsDir, 0755); err != nil { + t.Fatalf("Failed to create .beads dir: %v", err) + } + + // Create config.yaml with a prefix but NO database + configPath := filepath.Join(beadsDir, "config.yaml") + if err := os.WriteFile(configPath, []byte("prefix: test\n"), 0644); err != nil { + t.Fatalf("Failed to create config.yaml: %v", err) + } + + // Create empty issues.jsonl (simulates fresh clone) + jsonlPath := filepath.Join(beadsDir, "issues.jsonl") + if err := os.WriteFile(jsonlPath, []byte(""), 0644); err != nil { + t.Fatalf("Failed to create issues.jsonl: %v", err) + } + + // Test that IsYamlOnlyKey correctly identifies yaml-only keys + yamlOnlyKeys := []string{"no-db", "no-daemon", "no-auto-flush", "json", "sync.branch", "routing.mode"} + for _, key := range yamlOnlyKeys { + if !config.IsYamlOnlyKey(key) { + t.Errorf("Expected %q to be a yaml-only key", key) + } + } + + // Test that non-yaml-only keys are correctly identified + nonYamlKeys := []string{"jira.url", "linear.team_id", "status.custom"} + for _, key := range nonYamlKeys { + if config.IsYamlOnlyKey(key) { + t.Errorf("Expected %q to NOT be a yaml-only key", key) + } + } +} + // setupTestDB creates a temporary test database func setupTestDB(t *testing.T) (*sqlite.SQLiteStorage, func()) { tmpDir, err := os.MkdirTemp("", "bd-test-config-*") diff --git a/cmd/bd/main.go b/cmd/bd/main.go index 35c3b854..c593bba6 100644 --- a/cmd/bd/main.go +++ b/cmd/bd/main.go @@ -434,7 +434,14 @@ var rootCmd = &cobra.Command{ // Allow some commands to run without a database // - import: auto-initializes database if missing // - setup: creates editor integration files (no DB needed) - if cmd.Name() != "import" && cmd.Name() != "setup" { + // - config set/get for yaml-only keys: writes to config.yaml, not SQLite (GH#536) + isYamlOnlyConfigOp := false + if (cmd.Name() == "set" || cmd.Name() == "get") && cmd.Parent() != nil && cmd.Parent().Name() == "config" { + if len(args) > 0 && config.IsYamlOnlyKey(args[0]) { + isYamlOnlyConfigOp = true + } + } + if cmd.Name() != "import" && cmd.Name() != "setup" && !isYamlOnlyConfigOp { // No database found - provide context-aware error message fmt.Fprintf(os.Stderr, "Error: no beads database found\n")