{"record":{"id":"495624f1305b69fc","repo":"JuliusBrussee/caveman","slug":"migrate-sqlite-q-w-495624","errorCode":null,"errorMessage":"migrate sqlite %q: %w","messagePattern":"migrate sqlite %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"proxy/internal/store/store.go","lineNumber":272,"sourceCode":"\tif path == \":memory:\" {\n\t\treturn \"file::memory:?\" + pragmas\n\t}\n\t// A file: URI so a path containing '?' or '#' cannot be truncated into a\n\t// different database file by the driver's DSN split.\n\tu := url.URL{Scheme: \"file\", OmitHost: true, Path: path}\n\treturn u.String() + \"?\" + pragmas\n}\n\n// Open opens (creating if needed) the SQLite database at path and migrates the\n// schema. logger may be nil.\nfunc Open(path string, logger *slog.Logger) (*Store, error) {\n\tdb, err := sql.Open(\"sqlite\", sqliteDSN(path))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open sqlite %q: %w\", path, err)\n\t}\n\tif _, err := db.Exec(schema); err != nil {\n\t\t_ = db.Close()\n\t\treturn nil, fmt.Errorf(\"migrate sqlite %q: %w\", path, err)\n\t}\n\tif path != \":memory:\" {\n\t\t_ = os.Chmod(path, 0o600)\n\t}\n\tfor _, m := range migrations {\n\t\tif _, err := db.Exec(m); err != nil && !strings.Contains(err.Error(), \"duplicate column name\") {\n\t\t\t_ = db.Close()\n\t\t\treturn nil, fmt.Errorf(\"migrate sqlite %q: %w\", path, err)\n\t\t}\n\t}\n\treturn &Store{db: db, logger: logger}, nil\n}\n\n// Close closes the underlying database.\nfunc (s *Store) Close() error { return s.db.Close() }\n\n// Record persists one request row. It is best-effort: a write failure is logged\n// but never propagated, because telemetry must never break the operator's","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/store.go#L254-L290","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n$ caveman-proxy serve\nopen sqlite/migrate sqlite \"/home/u/.caveman/caveman.db\": attempt to write a readonly database\n\n# after\n$ mkdir -p ~/.caveman && chmod u+rwX ~/.caveman\n$ rm -f ~/.caveman/caveman.db  # optional: discard corrupt/legacy file\n$ caveman-proxy serve","handlingStrategy":"try-catch","validationCode":"// Pre-flight the DB location before Open\nif path != \":memory:\" {\n    if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {\n        return fmt.Errorf(\"db directory unwritable: %w\", err)\n    }\n    if f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600); err != nil {\n        return fmt.Errorf(\"db file unwritable: %w\", err)\n    } else { _ = f.Close() }\n}","typeGuard":null,"tryCatchPattern":"st, err := store.Open(path, logger)\nif err != nil {\n    if strings.Contains(err.Error(), \"migrate sqlite\") {\n        // distinguish readonly (fix perms) vs corrupt (recover/delete) via the wrapped error\n        if errors.Is(err, syscall.EROFS) || strings.Contains(err.Error(), \"readonly\") {\n            return fmt.Errorf(\"%s is read-only; fix permissions\", path)\n        }\n    }\n}","preventionTips":["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"],"tags":["sqlite","permissions","database","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}