gastownhall/beads · error

sealed legacy SQLite WAL does not match source fingerprint

Error message

sealed legacy SQLite WAL does not match source fingerprint

What it means

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.

Source

Thrown at internal/migration/legacysqlite/reader.go:147

		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"))
	}
	return sealedDB{dir: dir, db: filepath.Join(dir, "legacy.db"), source: resolved}, nil
}

func fingerprintSource(path string) (sourceSet, error) {
	db, err := fingerprintFile(path, true)
	if err != nil {
		return sourceSet{}, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure no process is using the legacy database (close bd sessions, stop daemons) before exporting
  2. If safe, checkpoint and remove the WAL first: run sqlite3 beads.db 'PRAGMA wal_checkpoint(TRUNCATE);' so no -wal exists during sealing
  3. Export from an offline snapshot copy of the database instead of the live file
  4. Serialize the export against any scheduled jobs that write to the database

Example fix

// before (live WAL-mode DB)
$ bd migrate --legacy ./beads.db --output ./issues.jsonl
// error: sealed legacy SQLite WAL does not match source fingerprint
// after (checkpoint WAL while quiesced)
$ sqlite3 beads.db 'PRAGMA wal_checkpoint(TRUNCATE);'
$ bd migrate --legacy ./beads.db --output ./issues.jsonl
Defensive patterns

Strategy: validation

Validate before calling

// checkpoint the WAL while quiesced so no -wal exists during export
func checkpointWAL(dbPath string) error {
	db, err := sql.Open("sqlite3", dbPath); if err != nil { return err }
	defer db.Close()
	_, err = db.Exec("PRAGMA wal_checkpoint(TRUNCATE)")
	return err
}

Try / catch

if err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil {
	if strings.Contains(err.Error(), "WAL does not match source fingerprint") {
		_ = checkpointWAL(src) // while no writers are active
		// retry export once
	}
	return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/2682be39740309c1. Report an issue: GitHub.