{"record":{"id":"57a482e03d777601","repo":"gastownhall/beads","slug":"legacy-sqlite-source-q-must-be-a-regular-file","errorCode":null,"errorMessage":"legacy SQLite source %q must be a regular file","messagePattern":"legacy SQLite source %q must be a regular file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":193,"sourceCode":"\tif err != nil {\n\t\treturn sourceSet{}, err\n\t}\n\treturn sourceSet{db, wal, journal}, nil\n}\n\nfunc fingerprintFile(path string, required bool) (fingerprint, error) {\n\tinfo, err := os.Lstat(path)\n\tif os.IsNotExist(err) && !required {\n\t\treturn fingerprint{}, nil\n\t}\n\tif err != nil {\n\t\treturn fingerprint{}, err\n\t}\n\tif info.Mode()&os.ModeSymlink != 0 {\n\t\treturn fingerprint{}, fmt.Errorf(\"legacy SQLite source %q must not be a symlink\", path)\n\t}\n\tif !info.Mode().IsRegular() {\n\t\treturn fingerprint{}, fmt.Errorf(\"legacy SQLite source %q must be a regular file\", path)\n\t}\n\tf, err := os.Open(path) //nolint:gosec // G304: source is lstat-checked and fingerprinted again after sealing.\n\tif err != nil {\n\t\treturn fingerprint{}, err\n\t}\n\tdefer f.Close()\n\th := sha256.New()\n\tif _, err = io.Copy(h, f); err != nil {\n\t\treturn fingerprint{}, err\n\t}\n\treturn fingerprint{true, info.Size(), info.ModTime(), hex.EncodeToString(h.Sum(nil)), info}, nil\n}\n\nfunc sameSet(a, b sourceSet) bool {\n\treturn sameFingerprint(a.db, b.db) && sameFingerprint(a.wal, b.wal) && sameFingerprint(a.journal, b.journal)\n}\nfunc sameFingerprint(a, b fingerprint) bool {\n\treturn 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)))","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the path with ls -l or stat and pass the actual regular .db file, not a directory or special file","If the database lives in a directory, use the path to beads.db inside it","Remove/recreate the entry if it is a leftover FIFO/socket: delete it and restore a real SQLite file from backup"],"exampleFix":"// before\n$ bd migrate --legacy ./legacy-data   # a directory\n// error: legacy SQLite source \"./legacy-data\" must be a regular file\n// after\n$ bd migrate --legacy ./legacy-data/beads.db ...","handlingStrategy":"validation","validationCode":"func requireRegularFile(path string) error {\n\tinfo, err := os.Stat(path)\n\tif err != nil { return err }\n\tif !info.Mode().IsRegular() {\n\t\treturn fmt.Errorf(\"%s is not a regular file (dir=%v, other)\", path, info.IsDir())\n\t}\n\treturn nil\n}\n// call before Export: requireRegularFile(src)","typeGuard":"func isPlainFile(path string) bool {\n\tinfo, err := os.Stat(path)\n\treturn err == nil && info.Mode().IsRegular()\n}","tryCatchPattern":"if !isPlainFile(src) {\n\treturn fmt.Errorf(\"--legacy must point at the .db file itself, got %q\", src)\n}\nif err := legacysqlite.Export(ctx, src, out, os.Stdout); err != nil { return err }","preventionTips":["Pass the .db file path, never a directory or special file","Verify with stat before wiring the path into automated migration jobs","Clean up leftover FIFOs/sockets that may occupy the beads.db name","Fix scripts that create the path so they write a regular file"],"tags":["sqlite","migration","path","validation"],"backgroundTag":"invalid-source-file-type","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}