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:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user