{"record":{"id":"2682be39740309c1","repo":"gastownhall/beads","slug":"sealed-legacy-sqlite-wal-does-not-match-source-fin","errorCode":null,"errorMessage":"sealed legacy SQLite WAL does not match source fingerprint","messagePattern":"sealed legacy SQLite WAL does not match source fingerprint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":147,"sourceCode":"\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}\n\treturn sealedDB{dir: dir, db: filepath.Join(dir, \"legacy.db\"), source: resolved}, nil\n}\n\nfunc fingerprintSource(path string) (sourceSet, error) {\n\tdb, err := fingerprintFile(path, true)\n\tif err != nil {\n\t\treturn sourceSet{}, err","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L129-L165","documentation":"Identical integrity check to the db-fingerprint error but applied to the copied WAL sidecar (legacy.db-wal). After copying the source WAL into the sealed directory, its SHA-256 must match the WAL fingerprint taken before the copy; otherwise the sealed snapshot would not represent a consistent point-in-time state of the source. Export fails and cleans up the temp directory.","triggerScenarios":"Export -> seal when before.wal.exists: the copied dir/legacy.db-wal digest differs from the pre-copy fingerprint. Happens when the source database is in WAL mode and a writer checkpoints/rewrites the -wal file during the copy window.","commonSituations":"A live bd process or another SQLite connection is writing to the WAL-mode legacy database while export runs; SQLite auto-checkpoint truncates/rewrites the WAL mid-copy; scheduled sync tools touching the sidecar.","solutions":["Ensure no process is using the legacy database (close bd sessions, stop daemons) before exporting","If safe, checkpoint and remove the WAL first: run sqlite3 beads.db 'PRAGMA wal_checkpoint(TRUNCATE);' so no -wal exists during sealing","Export from an offline snapshot copy of the database instead of the live file","Serialize the export against any scheduled jobs that write to the database"],"exampleFix":"// before (live WAL-mode DB)\n$ bd migrate --legacy ./beads.db --output ./issues.jsonl\n// error: sealed legacy SQLite WAL does not match source fingerprint\n// after (checkpoint WAL while quiesced)\n$ sqlite3 beads.db 'PRAGMA wal_checkpoint(TRUNCATE);'\n$ bd migrate --legacy ./beads.db --output ./issues.jsonl","handlingStrategy":"validation","validationCode":"// checkpoint the WAL while quiesced so no -wal exists during export\nfunc checkpointWAL(dbPath string) error {\n\tdb, err := sql.Open(\"sqlite3\", dbPath); if err != nil { return err }\n\tdefer db.Close()\n\t_, err = db.Exec(\"PRAGMA wal_checkpoint(TRUNCATE)\")\n\treturn err\n}","typeGuard":null,"tryCatchPattern":"if err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil {\n\tif strings.Contains(err.Error(), \"WAL does not match source fingerprint\") {\n\t\t_ = checkpointWAL(src) // while no writers are active\n\t\t// retry export once\n\t}\n\treturn err\n}","preventionTips":["Run PRAGMA wal_checkpoint(TRUNCATE) on a quiesced database so the -wal file is empty/absent during export","Ensure no other process holds the database open in WAL mode during export","Copy db+wal together offline and export from the copy","Avoid export while auto-checkpointing writers are active"],"tags":["sqlite","migration","wal","fingerprint"],"backgroundTag":"sealed-copy-fingerprint-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}