{"record":{"id":"2a426d5851fe6199","repo":"juanfont/headscale","slug":"adding-column-types-node-w","errorCode":null,"errorMessage":"adding column types.Node: %w","messagePattern":"adding column types\\.Node: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":154,"sourceCode":"\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"setting auth_key to null on nodes with non-existing keys: %w\", err)\n\t\t\t\t\t\t}\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// v0.26.0\n\t\t\t// Migrate all routes from the Route table to the new field ApprovedRoutes\n\t\t\t// in the Node table. Then drop the Route table.\n\t\t\t{\n\t\t\t\tID: \"202502131714\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\tif !tx.Migrator().HasColumn(&types.Node{}, \"approved_routes\") {\n\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.Node{}, \"approved_routes\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding column types.Node: %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tnodeRoutes := map[uint64][]netip.Prefix{}\n\n\t\t\t\t\tvar routes []types.Route //nolint:staticcheck // SA1019: Route kept for migrations\n\n\t\t\t\t\terr = tx.Find(&routes).Error\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"fetching routes: %w\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, route := range routes {\n\t\t\t\t\t\tif route.Enabled {\n\t\t\t\t\t\t\tnodeRoutes[route.NodeID] = append(nodeRoutes[route.NodeID], route.Prefix)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L136-L172","documentation":"Migration 202502131714 (moving routes from the routes table into nodes.approved_routes) failed adding the approved_routes column to nodes. DDL failure at this step is environmental: lock contention, missing ALTER privilege, insufficient disk for SQLite's table rewrite, or the column existing with an incompatible type (guarded by HasColumn, so re-runs are safe).","triggerScenarios":"ALTER TABLE nodes ADD COLUMN approved_routes rejected because another session locks nodes, the Postgres role lacks ALTER, or disk is full mid-rewrite.","commonSituations":"Large nodes table and low disk during SQLite migration; running the server while a long analytics query holds locks; restrictive DB grants in managed Postgres.","solutions":["Free disk space and stop concurrent database users, then restart headscale.","Grant ALTER on the database / UPDATE on nodes to the migration role.","Check pg_stat_activity or lsof on the SQLite file for lock holders.","If it failed mid-transaction, the rollback is automatic - simply retry after fixing the environment."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := db.NewHeadscaleDatabase(cfg); err != nil {\n    if strings.Contains(err.Error(), \"adding column types.Node\") {\n        // ALTER TABLE failed: clear locks, free disk, restart - HasColumn guard makes reruns safe\n    }\n}","preventionTips":["Back up before upgrading past v0.26.0.","Grant ALTER privileges on the nodes table to the migration role.","Free disk proportional to the nodes table size for SQLite DDL."],"tags":["database","migration","ddl","routes","sqlite"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}