{"record":{"id":"7be65319150549bc","repo":"gastownhall/beads","slug":"sealed-legacy-sqlite-database-does-not-match-sourc","errorCode":null,"errorMessage":"sealed legacy SQLite database does not match source fingerprint","messagePattern":"sealed legacy SQLite database does not match source fingerprint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":140,"sourceCode":"\t}\n\tdir, err := os.MkdirTemp(\"\", \"bd-legacy-sqlite-\")\n\tif err != nil {\n\t\treturn sealedDB{}, err\n\t}\n\tfail := func(err error) (sealedDB, error) { _ = os.RemoveAll(dir); return sealedDB{}, err }\n\tfor _, pair := range []struct {\n\t\tfrom, to string\n\t\tpresent  bool\n\t}{{resolved, filepath.Join(dir, \"legacy.db\"), true}, {resolved + \"-wal\", filepath.Join(dir, \"legacy.db-wal\"), before.wal.exists}} {\n\t\tif pair.present {\n\t\t\tif err := copyFile(pair.from, pair.to); err != nil {\n\t\t\t\treturn fail(err)\n\t\t\t}\n\t\t}\n\t}\n\tif copied, err := fingerprintFile(filepath.Join(dir, \"legacy.db\"), true); err != nil || copied.digest != before.db.digest {\n\t\tif err == nil {\n\t\t\terr = fmt.Errorf(\"sealed legacy SQLite database does not match source fingerprint\")\n\t\t}\n\t\treturn fail(err)\n\t}\n\tif before.wal.exists {\n\t\tif copied, err := fingerprintFile(filepath.Join(dir, \"legacy.db-wal\"), true); err != nil || copied.digest != before.wal.digest {\n\t\t\tif err == nil {\n\t\t\t\terr = fmt.Errorf(\"sealed legacy SQLite WAL does not match source fingerprint\")\n\t\t\t}\n\t\t\treturn fail(err)\n\t\t}\n\t}\n\tafter, err := fingerprintSource(resolved)\n\tif err != nil {\n\t\treturn fail(err)\n\t}\n\tif !sameSet(before, after) {\n\t\treturn fail(fmt.Errorf(\"legacy SQLite source changed while sealing\"))\n\t}","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L122-L158","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (live DB being written while exporting)\n$ bd migrate --legacy ./beads.db --output ./issues.jsonl\n// error: sealed legacy SQLite database does not match source fingerprint\n// after (quiesce first)\n$ bd daemon stop   # or otherwise ensure no process has beads.db open\n$ bd migrate --legacy ./beads.db --output ./issues.jsonl","handlingStrategy":"validation","validationCode":"// ensure the source is quiescent before Export\nfunc ensureQuiesced(dbPath string) error {\n\tfor i := 0; i < 2; i++ {\n\t\tf1, err := os.Open(dbPath); if err != nil { return err }\n\t\th1 := sha256.New(); io.Copy(h1, f1); f1.Close()\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tf2, _ := os.Open(dbPath); h2 := sha256.New(); io.Copy(h2, f2); f2.Close()\n\t\tif bytes.Equal(h1.Sum(nil), h2.Sum(nil)) { return nil }\n\t}\n\treturn fmt.Errorf(\"%s is being modified; stop writers before export\", dbPath)\n}","typeGuard":null,"tryCatchPattern":"// Go: check the error message and retry after quiescing\nif err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil {\n\tif strings.Contains(err.Error(), \"does not match source fingerprint\") ||\n\t\tstrings.Contains(err.Error(), \"changed while sealing\") {\n\t\t// stop writers, then retry once\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["sqlite","migration","integrity","fingerprint"],"backgroundTag":"sealed-copy-fingerprint-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}