Fix fresh clone UX with friendly error messages (bd-dmb)

When opening a database that exists but is missing issue_prefix config
(typical in fresh clone scenarios), show a helpful error message instead
of cryptic migration invariant errors.

The new message:
- Explains the database needs initialization
- Detects if a JSONL file exists and shows the issue count
- Suggests the exact command to run: bd import -i <path>
- Falls back to suggesting bd init --prefix if no JSONL exists

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-27 22:40:57 -08:00
parent 151a34be98
commit 8b8a662acb
4 changed files with 144 additions and 66 deletions

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"path/filepath"
"github.com/steveyegge/beads/internal/beads"
"github.com/steveyegge/beads/internal/debug"
@@ -67,6 +68,12 @@ func ensureStoreActive() error {
sqlStore, err := sqlite.New(rootCtx, dbPath)
if err != nil {
// Check for fresh clone scenario (bd-dmb)
if isFreshCloneError(err) {
beadsDir := filepath.Dir(dbPath)
handleFreshCloneError(err, beadsDir)
return fmt.Errorf("database not initialized")
}
return fmt.Errorf("failed to open database: %w", err)
}

View File

@@ -66,6 +66,11 @@ NOTE: Import requires direct database access and does not work with daemon mode.
var err error
store, err = sqlite.New(rootCtx, dbPath)
if err != nil {
// Check for fresh clone scenario (bd-dmb)
beadsDir := filepath.Dir(dbPath)
if handleFreshCloneError(err, beadsDir) {
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err)
os.Exit(1)
}

View File

@@ -9,6 +9,7 @@ import (
"runtime/pprof"
"runtime/trace"
"slices"
"strings"
"sync"
"syscall"
"time"
@@ -497,6 +498,11 @@ var rootCmd = &cobra.Command{
var err error
store, err = sqlite.New(rootCtx, dbPath)
if err != nil {
// Check for fresh clone scenario (bd-dmb)
beadsDir := filepath.Dir(dbPath)
if handleFreshCloneError(err, beadsDir) {
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err)
os.Exit(1)
}
@@ -603,3 +609,63 @@ func main() {
os.Exit(1)
}
}
// isFreshCloneError checks if the error is due to a fresh clone scenario
// where the database exists but is missing required config (like issue_prefix).
// This happens when someone clones a repo with beads but needs to initialize.
func isFreshCloneError(err error) bool {
if err == nil {
return false
}
errStr := err.Error()
// Check for the specific migration invariant error pattern
return strings.Contains(errStr, "post-migration validation failed") &&
strings.Contains(errStr, "required config key missing: issue_prefix")
}
// handleFreshCloneError displays a helpful message when a fresh clone is detected
// and returns true if the error was handled (so caller should exit).
// If not a fresh clone error, returns false and does nothing.
func handleFreshCloneError(err error, beadsDir string) bool {
if !isFreshCloneError(err) {
return false
}
// Look for JSONL file in the .beads directory
jsonlPath := ""
issueCount := 0
if beadsDir != "" {
// Check for issues.jsonl (canonical) first, then beads.jsonl (legacy)
for _, name := range []string{"issues.jsonl", "beads.jsonl"} {
candidate := filepath.Join(beadsDir, name)
if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
jsonlPath = candidate
// Count lines (approximately = issue count)
if data, readErr := os.ReadFile(candidate); readErr == nil {
for _, line := range strings.Split(string(data), "\n") {
if strings.TrimSpace(line) != "" {
issueCount++
}
}
}
break
}
}
}
fmt.Fprintf(os.Stderr, "Error: Database not initialized\n\n")
fmt.Fprintf(os.Stderr, "This appears to be a fresh clone or the database needs initialization.\n")
if jsonlPath != "" && issueCount > 0 {
fmt.Fprintf(os.Stderr, "Found: %s (%d issues)\n\n", jsonlPath, issueCount)
fmt.Fprintf(os.Stderr, "To initialize from the JSONL file, run:\n")
fmt.Fprintf(os.Stderr, " bd import -i %s\n\n", jsonlPath)
} else {
fmt.Fprintf(os.Stderr, "\nTo initialize a new database, run:\n")
fmt.Fprintf(os.Stderr, " bd init --prefix <your-prefix>\n\n")
}
fmt.Fprintf(os.Stderr, "For more information: bd init --help\n")
return true
}