JuliusBrussee/caveman · critical
migrate sqlite %q: %w
Error message
migrate sqlite %q: %w
What it means
Returned by store.Open when executing the base schema against the (newly or existing) SQLite database fails. This is the first real DB touch: it surfaces unusable/corrupt files, permission problems on the DB path, an incompatible SQLite file (e.g. created by a newer/wal-mode incompatible tool), or objects in the way (a table/view named like a schema table).
Source
Thrown at proxy/internal/store/store.go:272
if path == ":memory:" {
return "file::memory:?" + pragmas
}
// A file: URI so a path containing '?' or '#' cannot be truncated into a
// different database file by the driver's DSN split.
u := url.URL{Scheme: "file", OmitHost: true, Path: path}
return u.String() + "?" + pragmas
}
// Open opens (creating if needed) the SQLite database at path and migrates the
// schema. logger may be nil.
func Open(path string, logger *slog.Logger) (*Store, error) {
db, err := sql.Open("sqlite", sqliteDSN(path))
if err != nil {
return nil, fmt.Errorf("open sqlite %q: %w", path, err)
}
if _, err := db.Exec(schema); err != nil {
_ = db.Close()
return nil, fmt.Errorf("migrate sqlite %q: %w", path, err)
}
if path != ":memory:" {
_ = os.Chmod(path, 0o600)
}
for _, m := range migrations {
if _, err := db.Exec(m); err != nil && !strings.Contains(err.Error(), "duplicate column name") {
_ = db.Close()
return nil, fmt.Errorf("migrate sqlite %q: %w", path, err)
}
}
return &Store{db: db, logger: logger}, nil
}
// Close closes the underlying database.
func (s *Store) Close() error { return s.db.Close() }
// Record persists one request row. It is best-effort: a write failure is logged
// but never propagated, because telemetry must never break the operator'sView on GitHub (pinned to 27d5a3981a)
Solutions
- Verify write permission on the DB path AND its directory (SQLite creates wal/shm sidecars): chmod/prepare ~/.caveman
- If the file is corrupt, salvage with sqlite3 caveman.db .recover or simply delete it — telemetry history is the only loss
- Keep the DB on a local filesystem, never NFS/SMB shares
- If the schema clash comes from version drift, back up and remove the old DB so a fresh schema applies
Example fix
# before $ caveman-proxy serve open sqlite/migrate sqlite "/home/u/.caveman/caveman.db": attempt to write a readonly database # after $ mkdir -p ~/.caveman && chmod u+rwX ~/.caveman $ rm -f ~/.caveman/caveman.db # optional: discard corrupt/legacy file $ caveman-proxy serve
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight the DB location before Open
if path != ":memory:" {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return fmt.Errorf("db directory unwritable: %w", err)
}
if f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600); err != nil {
return fmt.Errorf("db file unwritable: %w", err)
} else { _ = f.Close() }
} Try / catch
st, err := store.Open(path, logger)
if err != nil {
if strings.Contains(err.Error(), "migrate sqlite") {
// distinguish readonly (fix perms) vs corrupt (recover/delete) via the wrapped error
if errors.Is(err, syscall.EROFS) || strings.Contains(err.Error(), "readonly") {
return fmt.Errorf("%s is read-only; fix permissions", path)
}
}
} Prevention
- Provision ~/.caveman (0700) before first run in install scripts
- Keep caveman.db on local disk — never NFS/SMB
- Run PRAGMA integrity_check in health checks for early corruption detection
When it happens
Trigger: Opening ~/.caveman/caveman.db that is corrupt (interrupted write without WAL), on a read-only filesystem or with a directory lacking write permission (SQLite needs to create -wal/-shm files), when a non-SQLite file sits at the path, or when another SQLite engine with different locking (older NFS-style locks) holds the file.
Common situations: First run on a locked-down machine where ~/.caveman is not writable; a crashed run leaving a hot journal the driver cannot recover; moving the DB to a network share; an old DB from a pre-migration schema colliding with the base schema DDL.
Related errors
- native session usage: %w
- ccr: typed object id collision
- memory %s changed during supersede
- prefix replacement put: %w
- open sqlite %q: %w
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/495624f1305b69fc.
Report an issue: GitHub.