{"record":{"id":"617f4da2b8c6b42b","repo":"juanfont/headscale","slug":"adding-description-to-pre-auth-keys-w","errorCode":null,"errorMessage":"adding description to pre_auth_keys: %w","messagePattern":"adding description to pre_auth_keys: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":809,"sourceCode":"\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.APIKey{}, \"user_id\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding user_id to api_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{\n\t\t\t\t// Add a free-text description to pre-auth keys, set via the\n\t\t\t\t// v2 keys API.\n\t\t\t\tID: \"202606191501-pre-auth-key-description\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\tif !tx.Migrator().HasColumn(&types.PreAuthKey{}, \"description\") {\n\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.PreAuthKey{}, \"description\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding description to pre_auth_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{\n\t\t\t\t// Add a revoked timestamp to pre-auth keys. The v2 API's DELETE\n\t\t\t\t// soft-revokes a key (set revoked = now) rather than destroying\n\t\t\t\t// it; the row is reaped later by the background collector.\n\t\t\t\tID: \"202606201200-pre-auth-key-revoked\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\tif !tx.Migrator().HasColumn(&types.PreAuthKey{}, \"revoked\") {\n\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.PreAuthKey{}, \"revoked\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding revoked to pre_auth_keys: %w\", err)\n\t\t\t\t\t\t}","sourceCodeStart":791,"sourceCodeEnd":827,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L791-L827","documentation":"Migration '202606191501-pre-auth-key-description' fails adding the free-text description column to pre_auth_keys (HasColumn-guarded AddColumn) for the v2 keys API. Identical failure class to the other AddColumn migrations: ALTER privilege missing, database locked, disk full, or drifted schema confusing the guard.","triggerScenarios":"Startup migration on a schema without pre_auth_keys.description while DDL execution is blocked or unauthorized.","commonSituations":"Upgrading alongside the v2 API rollout; constrained DB roles; shared SQLite databases; read-only database mounts.","solutions":["Resolve the wrapped underlying error (privileges / locks / disk)","Pre-apply ALTER TABLE pre_auth_keys ADD COLUMN description text DEFAULT '' if drift makes AddColumn fail, then restart - the HasColumn guard makes re-runs safe","Confirm the column post-startup with \\d pre_auth_keys or PRAGMA table_info(pre_auth_keys)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Confirm the database accepts DDL before the upgrade window\nif _, err := db.Exec(\"CREATE TEMP TABLE _p(id integer)\"); err != nil {\n\tlog.Fatalf(\"database not DDL-writable: %v\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Same hygiene as all AddColumn migrations: backup, single instance, DDL rights","Avoid mounting the SQLite file read-only in containers"],"tags":["database","migration","preauthkey","gorm","ddl"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}