gastownhall/beads · error
sealed legacy SQLite database does not match source fingerpr
Error message
sealed legacy SQLite database does not match source fingerprint
What it means
During Export, the legacysqlite package first 'seals' the legacy SQLite database by copying it (and its WAL) into a private temp directory. After copying, it re-fingerprints (SHA-256) the copied legacy.db and compares the digest to the fingerprint taken of the source before the copy. This error means the bytes written to the sealed copy differ from the source bytes, so the seal is untrustworthy and Export aborts and removes the temp dir. It is a deliberate integrity check, not a data corruption report about your database itself.
Source
Thrown at internal/migration/legacysqlite/reader.go:140
}
dir, err := os.MkdirTemp("", "bd-legacy-sqlite-")
if err != nil {
return sealedDB{}, err
}
fail := func(err error) (sealedDB, error) { _ = os.RemoveAll(dir); return sealedDB{}, err }
for _, pair := range []struct {
from, to string
present bool
}{{resolved, filepath.Join(dir, "legacy.db"), true}, {resolved + "-wal", filepath.Join(dir, "legacy.db-wal"), before.wal.exists}} {
if pair.present {
if err := copyFile(pair.from, pair.to); err != nil {
return fail(err)
}
}
}
if copied, err := fingerprintFile(filepath.Join(dir, "legacy.db"), true); err != nil || copied.digest != before.db.digest {
if err == nil {
err = fmt.Errorf("sealed legacy SQLite database does not match source fingerprint")
}
return fail(err)
}
if before.wal.exists {
if copied, err := fingerprintFile(filepath.Join(dir, "legacy.db-wal"), true); err != nil || copied.digest != before.wal.digest {
if err == nil {
err = fmt.Errorf("sealed legacy SQLite WAL does not match source fingerprint")
}
return fail(err)
}
}
after, err := fingerprintSource(resolved)
if err != nil {
return fail(err)
}
if !sameSet(before, after) {
return fail(fmt.Errorf("legacy SQLite source changed while sealing"))
}View on GitHub (pinned to 71377f2769)
Solutions
- Stop all writers to the legacy SQLite database (bd daemons, sync loops) and re-run the export
- Verify a stable source: copy the .db (and -wal) manually with cp while quiesced, run sqlite3 'PRAGMA integrity_check', and export from the snapshot
- Check for concurrent automation (cron, CI, git hooks) touching the file and serialize the export
- If on a network/synced filesystem, copy the database to local disk first and point Export at the local copy
Example fix
// before (live DB being written while exporting) $ bd migrate --legacy ./beads.db --output ./issues.jsonl // error: sealed legacy SQLite database does not match source fingerprint // after (quiesce first) $ bd daemon stop # or otherwise ensure no process has beads.db open $ bd migrate --legacy ./beads.db --output ./issues.jsonl
Defensive patterns
Strategy: validation
Validate before calling
// ensure the source is quiescent before Export
func ensureQuiesced(dbPath string) error {
for i := 0; i < 2; i++ {
f1, err := os.Open(dbPath); if err != nil { return err }
h1 := sha256.New(); io.Copy(h1, f1); f1.Close()
time.Sleep(200 * time.Millisecond)
f2, _ := os.Open(dbPath); h2 := sha256.New(); io.Copy(h2, f2); f2.Close()
if bytes.Equal(h1.Sum(nil), h2.Sum(nil)) { return nil }
}
return fmt.Errorf("%s is being modified; stop writers before export", dbPath)
} Try / catch
// Go: check the error message and retry after quiescing
if err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil {
if strings.Contains(err.Error(), "does not match source fingerprint") ||
strings.Contains(err.Error(), "changed while sealing") {
// stop writers, then retry once
}
return err
} Prevention
- Stop bd daemons, sync jobs, and other SQLite clients before exporting
- Never export from a database living in a cloud-synced or network-mounted folder; copy it locally first
- Schedule exports outside windows when writers/compaction run
- Take a filesystem snapshot or offline copy and export from that
When it happens
Trigger: Export -> seal copies source db to a temp dir, then fingerprintFile(dir/legacy.db, true).digest != before.db.digest. Practically: the source file was modified while copyFile was reading it, filesystem/cache inconsistency during the copy, or an I/O path (NFS, FUSE, snapshot mount) returning different bytes across reads.
Common situations: Another bd/SQLite process writes to the .db during export; copying from a live-synced cloud folder (Dropbox/Drive) whose contents shift mid-read; flaky storage or container volume mounts that don't provide stable reads; running Export concurrently with a migration/compaction job.
Related errors
- sealed legacy SQLite WAL does not match source fingerprint
- legacy SQLite source changed while sealing
- legacy SQLite source %q must not be a symlink
- legacy SQLite source %q must be a regular file
- --output must not alias legacy SQLite source or sidecar
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7be65319150549bc.
Report an issue: GitHub.