{"record":{"id":"695ce66155aa20f9","repo":"juanfont/headscale","slug":"disabling-foreign-keys-w","errorCode":null,"errorMessage":"disabling foreign keys: %w","messagePattern":"disabling foreign keys: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/db.go","lineNumber":1170,"sourceCode":"\n\treturn nil, fmt.Errorf(\n\t\t\"database of type %s is not supported: %w\",\n\t\tcfg.Type,\n\t\terrDatabaseNotSupported,\n\t)\n}\n\nfunc runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormigrate.Gormigrate) error {\n\tif cfg.Type == types.DatabaseSqlite {\n\t\t// SQLite: Run the early migrations that GORM cannot handle safely with\n\t\t// foreign keys enabled (route and pre-auth-key automigrations) with FK\n\t\t// disabled, then run everything else with FK enabled.\n\t\t//\n\t\t// NO NEW MIGRATIONS SHOULD RUN WITH FK DISABLED. As of 2025-07-02, all\n\t\t// new migrations must run with foreign keys enabled via the\n\t\t// migrations.Migrate() call below.\n\t\tif err := dbConn.Exec(\"PRAGMA foreign_keys = OFF\").Error; err != nil { //nolint:noinlineerr\n\t\t\treturn fmt.Errorf(\"disabling foreign keys: %w\", err)\n\t\t}\n\n\t\t// Run up to and including the last migration that requires FK disabled.\n\t\tif err := migrations.MigrateTo(\"202501311657\"); err != nil { //nolint:noinlineerr\n\t\t\treturn fmt.Errorf(\"running migration 202501311657: %w\", err)\n\t\t}\n\n\t\tif err := dbConn.Exec(\"PRAGMA foreign_keys = ON\").Error; err != nil { //nolint:noinlineerr\n\t\t\treturn fmt.Errorf(\"restoring foreign keys: %w\", err)\n\t\t}\n\n\t\t// Run the rest of the migrations\n\t\tif err := migrations.Migrate(); err != nil { //nolint:noinlineerr\n\t\t\treturn err\n\t\t}\n\n\t\t// Check for constraint violations at the end\n\t\ttype constraintViolation struct {","sourceCodeStart":1152,"sourceCodeEnd":1188,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L1152-L1188","documentation":"On sqlite, headscale runs the frozen set of pre-2025-07-02 early migrations with PRAGMA foreign_keys = OFF (these old automigrations cannot run with FKs enabled). This error means the PRAGMA statement itself failed — the connection could not even accept the pragma, typically because the database is locked or the connection is bad.","triggerScenarios":"Another process holds an exclusive lock on the sqlite file at startup (second headscale instance, litestream in certain modes, an open sqlite3 shell in a transaction); or the file is corrupt enough that any statement fails.","commonSituations":"Accidentally running two headscale replicas against one sqlite file; a leftover lock file after a crash; the DB file on NFS or a filesystem without proper locking.","solutions":["Ensure exactly one headscale process opens the sqlite database; stop duplicates.","Close interactive sqlite3 sessions and let litestream run in replication-only mode.","Move the sqlite file off network filesystems onto local disk.","If a stale -wal/-shm or lock persists after all holders are gone, copy the DB aside and verify integrity with sqlite3 db 'PRAGMA integrity_check;'."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Detect an existing holder before startup (sqlite):\n// lsof /var/lib/headscale/headscale.db  # expect no output besides this process\n// fuser -v /var/lib/headscale/headscale.db","typeGuard":null,"tryCatchPattern":"// If wrapped error contains 'database is locked', restart with backoff\n// after stopping the conflicting process. Otherwise investigate corruption.","preventionTips":["One writer per sqlite file, always.","Avoid NFS/network filesystems for sqlite.","Set sqlite busy_timeout via config if transient locks are expected."],"tags":["database","sqlite","pragma","locking","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}