fix: Fix daemon export leaving JSONL newer than database (issues #301, #321)

After daemon auto-export, JSONL mtime could be newer than database mtime
due to SQLite WAL mode not updating beads.db until checkpoint. This caused
validatePreExport to incorrectly block subsequent exports with "JSONL is
newer than database" error, leading to daemon shutdown.

Solution: Call TouchDatabaseFile after all export operations to ensure
database mtime >= JSONL mtime. This prevents false positives in validation
This commit is contained in:
Charles P. Cross
2025-11-18 17:25:32 -05:00
parent aeea3532cd
commit 04a1996fd9
6 changed files with 298 additions and 12 deletions

View File

@@ -314,7 +314,7 @@ NOTE: Import requires direct database access and does not work with daemon mode.
// 2. Without mtime update, bd sync refuses to export (thinks JSONL is newer)
// 3. This can happen after git pull updates JSONL mtime but content is identical
// Fix for: refusing to export: JSONL is newer than database (import first to avoid data loss)
if err := touchDatabaseFile(dbPath, input); err != nil {
if err := TouchDatabaseFile(dbPath, input); err != nil {
debug.Logf("Warning: failed to update database mtime: %v", err)
}
@@ -381,17 +381,19 @@ NOTE: Import requires direct database access and does not work with daemon mode.
},
}
// touchDatabaseFile updates the modification time of the database file.
// This is used after import to ensure the database appears "in sync" with JSONL,
// preventing bd doctor from incorrectly warning that JSONL is newer.
// TouchDatabaseFile updates the modification time of the database file.
// This is used after import AND export to ensure the database appears "in sync" with JSONL,
// preventing bd doctor and validatePreExport from incorrectly warning that JSONL is newer.
//
// In SQLite WAL mode, writes go to beads.db-wal and beads.db mtime may not update
// until a checkpoint. Since bd doctor compares JSONL mtime to beads.db mtime only,
// we need to explicitly touch the DB file after import.
// until a checkpoint. Since validation compares JSONL mtime to beads.db mtime only,
// we need to explicitly touch the DB file after both import and export operations.
//
// The function sets DB mtime to max(JSONL mtime, now) + 1ns to handle clock skew.
// If jsonlPath is empty or can't be read, falls back to time.Now().
func touchDatabaseFile(dbPath, jsonlPath string) error {
//
// Fixes issues #278, #301, #321: daemon export leaving JSONL newer than DB.
func TouchDatabaseFile(dbPath, jsonlPath string) error {
targetTime := time.Now()
// If we have the JSONL path, use max(JSONL mtime, now) to handle clock skew