{"record":{"id":"c08e1751acf42cf5","repo":"juanfont/headscale","slug":"automigrating-types-route-w","errorCode":null,"errorMessage":"automigrating types.Route: %w","messagePattern":"automigrating types\\.Route: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":97,"sourceCode":"\t\t\t\t\t// Remove any invalid routes associated with a node that does not exist.\n\t\t\t\t\tif tx.Migrator().HasTable(&types.Route{}) && tx.Migrator().HasTable(&types.Node{}) { //nolint:staticcheck // SA1019: Route kept for migrations\n\t\t\t\t\t\terr := tx.Exec(\"delete from routes where node_id not in (select id from nodes)\").Error\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove any invalid routes without a node_id.\n\t\t\t\t\tif tx.Migrator().HasTable(&types.Route{}) { //nolint:staticcheck // SA1019: Route kept for migrations\n\t\t\t\t\t\terr := tx.Exec(\"delete from routes where node_id is null\").Error\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\terr := tx.AutoMigrate(&types.Route{}) //nolint:staticcheck // SA1019: Route kept for migrations\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"automigrating types.Route: %w\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t\tRollback: func(db *gorm.DB) error { return nil },\n\t\t\t},\n\t\t\t// Add back constraint so you cannot delete preauth keys that\n\t\t\t// is still used by a node.\n\t\t\t{\n\t\t\t\tID: \"202501311657\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\terr := tx.AutoMigrate(&types.PreAuthKey{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"automigrating types.PreAuthKey: %w\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\terr = tx.AutoMigrate(&types.Node{})\n\t\t\t\t\tif err != nil {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L79-L115","documentation":"Migration-time error: tx.AutoMigrate(&types.Route{}) failed while cleaning up the legacy routes table (the migration first deletes rows with null node_id, then re-aligns the table with the Go struct). Route is deprecated and kept only for migrations, so failure is almost always environmental or schema-drift, not application logic.","triggerScenarios":"Upgrading a database where the routes table has an incompatible structure (columns with wrong types, indexes that block DDL), the DB user lacks ALTER privileges, or SQLite file is locked/read-only.","commonSituations":"Upgrading from an old/forked headscale whose routes schema differs; running migrations on a read-only SQLite file; Postgres user missing ALTER TABLE rights.","solutions":["Check the wrapped driver error for the exact DDL failure (permission, lock, type mismatch).","Back up the database, then inspect the routes table schema and reconcile it with types.Route.","Ensure exclusive access during startup migrations (stop other headscale instances).","Restore from backup and upgrade stepwise through supported versions if schema drift is severe."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"hsdb, err := db.NewHeadscaleDatabase(cfg)\nif err != nil {\n    // migrations are transactional; fix the environment (locks/permissions/disk) and restart\n    log.Fatal().Err(err).Msg(\"migration failed; database unchanged - resolve the wrapped error and restart\")\n}","preventionTips":["Take a file-level backup before upgrading (SQLite: copy the .db while headscale is stopped).","Give the migration role full ALTER/DDL privileges on the database.","Ensure exclusive database access during startup; stop old instances first."],"tags":["database","migration","gorm","sqlite","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}