gastownhall/beads · error

legacy SQLite source %q must be a regular file

Error message

legacy SQLite source %q must be a regular file

What it means

After rejecting symlinks, fingerprintFile also requires the path to be a regular file (info.Mode().IsRegular()). Directories, device nodes, sockets, FIFOs, and other special files cannot be safely copied and fingerprinted as a SQLite database, so Export refuses with this error before touching the data.

Source

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

	if err != nil {
		return sourceSet{}, err
	}
	return sourceSet{db, wal, journal}, nil
}

func fingerprintFile(path string, required bool) (fingerprint, error) {
	info, err := os.Lstat(path)
	if os.IsNotExist(err) && !required {
		return fingerprint{}, nil
	}
	if err != nil {
		return fingerprint{}, err
	}
	if info.Mode()&os.ModeSymlink != 0 {
		return fingerprint{}, fmt.Errorf("legacy SQLite source %q must not be a symlink", path)
	}
	if !info.Mode().IsRegular() {
		return fingerprint{}, fmt.Errorf("legacy SQLite source %q must be a regular file", path)
	}
	f, err := os.Open(path) //nolint:gosec // G304: source is lstat-checked and fingerprinted again after sealing.
	if err != nil {
		return fingerprint{}, err
	}
	defer f.Close()
	h := sha256.New()
	if _, err = io.Copy(h, f); err != nil {
		return fingerprint{}, err
	}
	return fingerprint{true, info.Size(), info.ModTime(), hex.EncodeToString(h.Sum(nil)), info}, nil
}

func sameSet(a, b sourceSet) bool {
	return sameFingerprint(a.db, b.db) && sameFingerprint(a.wal, b.wal) && sameFingerprint(a.journal, b.journal)
}
func sameFingerprint(a, b fingerprint) bool {
	return a.exists == b.exists && (!a.exists || (a.size == b.size && a.mod.Equal(b.mod) && a.digest == b.digest && os.SameFile(a.info, b.info)))

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the path with ls -l or stat and pass the actual regular .db file, not a directory or special file
  2. If the database lives in a directory, use the path to beads.db inside it
  3. Remove/recreate the entry if it is a leftover FIFO/socket: delete it and restore a real SQLite file from backup

Example fix

// before
$ bd migrate --legacy ./legacy-data   # a directory
// error: legacy SQLite source "./legacy-data" must be a regular file
// after
$ bd migrate --legacy ./legacy-data/beads.db ...
Defensive patterns

Strategy: validation

Validate before calling

func requireRegularFile(path string) error {
	info, err := os.Stat(path)
	if err != nil { return err }
	if !info.Mode().IsRegular() {
		return fmt.Errorf("%s is not a regular file (dir=%v, other)", path, info.IsDir())
	}
	return nil
}
// call before Export: requireRegularFile(src)

Type guard

func isPlainFile(path string) bool {
	info, err := os.Stat(path)
	return err == nil && info.Mode().IsRegular()
}

Try / catch

if !isPlainFile(src) {
	return fmt.Errorf("--legacy must point at the .db file itself, got %q", src)
}
if err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil { return err }

Prevention

When it happens

Trigger: Export -> seal -> fingerprintSource -> fingerprintFile where Lstat succeeds but the mode is not a regular file and not a symlink — e.g. the source path is a directory, a named pipe, a device node, or (for required paths like the .db) any non-regular entry.

Common situations: Typo where the user passes a directory (or the repo root) as the legacy source; passing a device or socket by mistake; a broken setup where beads.db is a FIFO created by a script.

Related errors


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