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 <noreply@anthropic.com>
This commit is contained in:
beads/crew/fang
2026-01-02 00:01:58 -08:00
committed by Steve Yegge
parent 8601ed01b6
commit 6572654cdc
2 changed files with 55 additions and 1 deletions

View File

@@ -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-*")

View File

@@ -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")