feat: Enhance bd doctor with force repair and source selection
Add comprehensive database corruption recovery capabilities to bd doctor. ## Changes ### New Command Flags - --force: Force repair mode that bypasses database validation - --source: Choose source of truth (auto/jsonl/db) for recovery ### Enhanced Error Classification Improved CheckDatabaseIntegrity() to detect and classify: - Database locked errors (suggests killing processes, removing locks) - Invalid SQLite files (suggests JSONL recovery with exact commands) - Migration/validation failures (orphaned dependencies, etc.) - Generic database errors (context-aware suggestions) Each error type provides: - Specific diagnosis - Step-by-step recovery instructions - Appropriate command examples with new flags ### Force Recovery Implementation New DatabaseCorruptionRecoveryWithOptions() function: - Bypasses database validation when --force is used - Supports explicit source of truth selection - Auto-detects best recovery path when source=auto - Comprehensive rollback on failure - Uses --force --no-git-history in import during force mode ### Integration Updated fix orchestration to pass force and source flags to recovery. ## Usage Examples ```bash # Unopenable database with validation errors bd doctor --fix --force --source=jsonl # Choose specific source of truth bd doctor --fix --source=jsonl # Trust JSONL bd doctor --fix --source=db # Trust database bd doctor --fix --source=auto # Auto-detect (default) # Force recovery with auto-detection bd doctor --fix --force ``` ## Problem Solved Before: When database had validation errors (orphaned dependencies, foreign key violations), all bd commands failed in a catch-22 situation. Could not open database to fix database. Users had to manually delete database files and reinit. After: bd doctor --fix --force detects unopenable databases, provides clear recovery steps, and forces rebuild from JSONL even when database validation fails. ## Backward Compatibility - All new flags are optional with safe defaults - --source defaults to 'auto' (existing behavior) - --force is opt-in only - Existing bd doctor behavior unchanged when flags not used - DatabaseCorruptionRecovery() still exists for compatibility Fixes: bd-pgza
This commit is contained in:
@@ -249,28 +249,66 @@ func CheckDatabaseIntegrity(path string) DoctorCheck {
|
||||
// Open database in read-only mode for integrity check
|
||||
db, err := sql.Open("sqlite3", sqliteConnString(dbPath, true))
|
||||
if err != nil {
|
||||
// Classify the error type for better recovery guidance
|
||||
errMsg := err.Error()
|
||||
var errorType string
|
||||
var recoverySteps string
|
||||
|
||||
// Check if JSONL recovery is possible
|
||||
jsonlCount, _, jsonlErr := CountJSONLIssues(filepath.Join(beadsDir, "issues.jsonl"))
|
||||
if jsonlErr != nil {
|
||||
jsonlCount, _, jsonlErr = CountJSONLIssues(filepath.Join(beadsDir, "beads.jsonl"))
|
||||
}
|
||||
|
||||
if jsonlErr == nil && jsonlCount > 0 {
|
||||
return DoctorCheck{
|
||||
Name: "Database Integrity",
|
||||
Status: StatusError,
|
||||
Message: fmt.Sprintf("Failed to open database (JSONL has %d issues for recovery)", jsonlCount),
|
||||
Detail: err.Error(),
|
||||
Fix: "Run 'bd doctor --fix' to recover from JSONL backup",
|
||||
// Classify error type
|
||||
if strings.Contains(errMsg, "database is locked") {
|
||||
errorType = "Database is locked"
|
||||
recoverySteps = "1. Check for running bd processes: ps aux | grep bd\n" +
|
||||
"2. Kill any stale processes\n" +
|
||||
"3. Remove stale locks: rm .beads/beads.db-shm .beads/beads.db-wal .beads/daemon.lock\n" +
|
||||
"4. Retry: bd doctor --fix"
|
||||
} else if strings.Contains(errMsg, "not a database") || strings.Contains(errMsg, "file is not a database") {
|
||||
errorType = "File is not a valid SQLite database"
|
||||
if jsonlErr == nil && jsonlCount > 0 {
|
||||
recoverySteps = fmt.Sprintf("Database file is corrupted beyond repair.\n\n"+
|
||||
"Recovery steps:\n"+
|
||||
"1. Backup corrupt database: mv .beads/beads.db .beads/beads.db.broken\n"+
|
||||
"2. Rebuild from JSONL (%d issues): bd doctor --fix --force --source=jsonl\n"+
|
||||
"3. Verify: bd stats", jsonlCount)
|
||||
} else {
|
||||
recoverySteps = "Database file is corrupted and no JSONL backup found.\n" +
|
||||
"Manual recovery required:\n" +
|
||||
"1. Restore from git: git checkout HEAD -- .beads/issues.jsonl\n" +
|
||||
"2. Rebuild database: bd doctor --fix --force"
|
||||
}
|
||||
} else if strings.Contains(errMsg, "migration") || strings.Contains(errMsg, "validation failed") {
|
||||
errorType = "Database migration or validation failed"
|
||||
if jsonlErr == nil && jsonlCount > 0 {
|
||||
recoverySteps = fmt.Sprintf("Database has validation errors (possibly orphaned dependencies).\n\n"+
|
||||
"Recovery steps:\n"+
|
||||
"1. Backup database: mv .beads/beads.db .beads/beads.db.broken\n"+
|
||||
"2. Rebuild from JSONL (%d issues): bd doctor --fix --force --source=jsonl\n"+
|
||||
"3. Verify: bd stats\n\n"+
|
||||
"Alternative: bd doctor --fix --force (attempts to repair in-place)", jsonlCount)
|
||||
} else {
|
||||
recoverySteps = "Database validation failed and no JSONL backup available.\n" +
|
||||
"Try: bd doctor --fix --force"
|
||||
}
|
||||
} else {
|
||||
errorType = "Failed to open database"
|
||||
if jsonlErr == nil && jsonlCount > 0 {
|
||||
recoverySteps = fmt.Sprintf("Run 'bd doctor --fix --source=jsonl' to rebuild from JSONL (%d issues)", jsonlCount)
|
||||
} else {
|
||||
recoverySteps = "Run 'bd doctor --fix --force' to attempt recovery"
|
||||
}
|
||||
}
|
||||
|
||||
return DoctorCheck{
|
||||
Name: "Database Integrity",
|
||||
Status: StatusError,
|
||||
Message: "Failed to open database for integrity check",
|
||||
Detail: err.Error(),
|
||||
Fix: "Run 'bd doctor --fix' to back up the corrupt DB and rebuild from JSONL (if available), or restore from backup",
|
||||
Message: errorType,
|
||||
Detail: fmt.Sprintf("%s\n\nError: %s", recoverySteps, errMsg),
|
||||
Fix: "See recovery steps above",
|
||||
}
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
Reference in New Issue
Block a user