From a1e507520c8df13a6add23b66602961288747b6f Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 20 Nov 2025 20:33:52 -0500 Subject: [PATCH] Fix bd-ca0b: bd sync now auto-resolves conflicts instead of failing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, bd sync would fail with "Pre-export validation failed: refusing to export: JSONL is newer than database" when JSONL was modified (e.g., after git pull). Now bd sync intelligently handles this by: - Detecting when JSONL is newer than the database - Automatically importing before exporting - Continuing with the normal sync flow This makes the workflow much smoother - users can just run 'bd sync' and it figures out what needs to be done. Changes: - Added isJSONLNewer() helper to check file timestamps - Modified sync command to auto-import when JSONL is newer - Extracted timestamp check logic for reusability Resolves: bd-ca0b 🤖 Generated with Claude Code Co-Authored-By: Claude --- cmd/bd/integrity.go | 33 ++++++++++++++++++++++----------- cmd/bd/sync.go | 10 ++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/cmd/bd/integrity.go b/cmd/bd/integrity.go index 2c54ad7e..77ecd4c1 100644 --- a/cmd/bd/integrity.go +++ b/cmd/bd/integrity.go @@ -12,23 +12,34 @@ import ( "github.com/steveyegge/beads/internal/types" ) +// isJSONLNewer checks if JSONL file is newer than database file. +// Returns true if JSONL is newer, false otherwise. +func isJSONLNewer(jsonlPath string) bool { + jsonlInfo, jsonlStatErr := os.Stat(jsonlPath) + if jsonlStatErr != nil { + return false + } + + beadsDir := filepath.Dir(jsonlPath) + dbPath := filepath.Join(beadsDir, "beads.db") + dbInfo, dbStatErr := os.Stat(dbPath) + if dbStatErr != nil { + return false + } + + return jsonlInfo.ModTime().After(dbInfo.ModTime()) +} + // validatePreExport performs integrity checks before exporting database to JSONL. // Returns error if critical issues found that would cause data loss. func validatePreExport(ctx context.Context, store storage.Storage, jsonlPath string) error { // Check if JSONL is newer than database - if so, must import first - jsonlInfo, jsonlStatErr := os.Stat(jsonlPath) - if jsonlStatErr == nil { - beadsDir := filepath.Dir(jsonlPath) - dbPath := filepath.Join(beadsDir, "beads.db") - dbInfo, dbStatErr := os.Stat(dbPath) - if dbStatErr == nil { - // If JSONL is newer, refuse export - caller must import first - if jsonlInfo.ModTime().After(dbInfo.ModTime()) { - return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)") - } - } + if isJSONLNewer(jsonlPath) { + return fmt.Errorf("refusing to export: JSONL is newer than database (import first to avoid data loss)") } + jsonlInfo, jsonlStatErr := os.Stat(jsonlPath) + // Get database issue count (fast path with COUNT(*) if available) dbCount, err := countDBIssuesFast(ctx, store) if err != nil { diff --git a/cmd/bd/sync.go b/cmd/bd/sync.go index bbb751d4..07586456 100644 --- a/cmd/bd/sync.go +++ b/cmd/bd/sync.go @@ -126,6 +126,16 @@ Use --merge to merge the sync branch back to main branch.`, if dryRun { fmt.Println("→ [DRY RUN] Would export pending changes to JSONL") } else { + // Smart conflict resolution: if JSONL is newer, auto-import first + if isJSONLNewer(jsonlPath) { + fmt.Println("→ JSONL is newer than database, importing first...") + if err := importFromJSONL(ctx, jsonlPath, renameOnImport); err != nil { + fmt.Fprintf(os.Stderr, "Error auto-importing: %v\n", err) + os.Exit(1) + } + fmt.Println("✓ Auto-import complete") + } + // Pre-export integrity checks if err := ensureStoreActive(); err == nil && store != nil { if err := validatePreExport(ctx, store, jsonlPath); err != nil {