{"record":{"id":"0a06172b4999e7ff","repo":"juanfont/headscale","slug":"foreign-key-constraints-violated","errorCode":null,"errorMessage":"foreign key constraints violated","messagePattern":"foreign key constraints violated","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":38,"sourceCode":"\t\"github.com/juanfont/headscale/hscontrol/util\"\n\t\"github.com/rs/zerolog/log\"\n\t\"github.com/tailscale/squibble\"\n\t\"gorm.io/driver/postgres\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/logger\"\n\t\"gorm.io/gorm/schema\"\n)\n\n//go:embed schema.sql\nvar dbSchema string\n\nfunc init() {\n\tschema.RegisterSerializer(\"text\", TextSerialiser{})\n}\n\nvar errDatabaseNotSupported = errors.New(\"database type not supported\")\n\nvar errForeignKeyConstraintsViolated = errors.New(\"foreign key constraints violated\")\n\nconst (\n\tmaxIdleConns   = 100\n\tmaxOpenConns   = 100\n\tcontextTimeout = 10 * time.Second\n)\n\ntype HSDatabase struct {\n\tDB  *gorm.DB\n\tcfg *types.Config\n}\n\n// NewHeadscaleDatabase creates a new database connection and runs migrations.\n// It accepts the full configuration to allow migrations access to policy settings.\n//\n//nolint:gocyclo // complex database initialization with many migrations\nfunc NewHeadscaleDatabase(cfg *types.Config) (*HSDatabase, error) {\n\tdbConn, err := openDB(cfg.Database)","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L20-L56","documentation":"Returned at the end of SQLite migrations (hscontrol/db/db.go:1218-1228) when PRAGMA foreign_key_check reports orphaned rows after the migration run. Each violation is logged with table, row_id, and parent table before the sentinel is returned, so the log lines immediately above the error identify the exact rows. It means the database contains rows referencing non-existent parents (e.g. a node with a user_id that has no users row).","triggerScenarios":"Starting headscale against a SQLite file that was previously migrated with foreign keys disabled, hand-edited, partially restored from backup, or produced by a very old version with known FK gaps; the post-migration PRAGMA foreign_key_check then finds orphans.","commonSituations":"Restoring a dump that skipped parent tables; a crashed migration leaving half-written rows; databases touched by external scripts that inserted child rows directly.","solutions":["Read the 'Foreign key constraint violated' log lines immediately above the error to identify table/row_id/parent","Restore from a clean backup taken before the corruption","Repair by deleting the orphaned child rows (e.g. nodes whose user_id has no matching users row) after backing up the SQLite file","If the DB is disposable, move/delete the sqlite file and let headscale recreate the schema"],"exampleFix":"-- identify orphans (example for nodes)\nSELECT n.id, n.user_id FROM nodes n\nLEFT JOIN users u ON u.id = n.user_id\nWHERE u.id IS NULL;\n-- after backing up, remove or repair them, then restart headscale","handlingStrategy":"validation","validationCode":"-- run before upgrading/restarting against a suspect DB:\nPRAGMA foreign_key_check;\n-- if it returns rows, fix them before letting headscale migrate","typeGuard":null,"tryCatchPattern":"if err := db.HeadscaleDBMigrations(...); err != nil {\n    if errors.Is(err, errForeignKeyConstraintsViolated) {\n        // log lines above list table/row_id/parent — route to a data-repair runbook\n        return fmt.Errorf(\"orphaned rows detected; run PRAGMA foreign_key_check and repair: %w\", err)\n    }\n    return err\n}","preventionTips":["Back up the SQLite file before every headscale upgrade","Never insert child rows (nodes, routes, keys) with direct SQL","Periodically run PRAGMA foreign_key_check on production databases"],"tags":["database","sqlite","migration","data-integrity","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}