{"record":{"id":"f88899a1697b92e1","repo":"juanfont/headscale","slug":"adding-hash-column-w","errorCode":null,"errorMessage":"adding hash column: %w","messagePattern":"adding hash column: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":529,"sourceCode":"\n\t\t\t{\n\t\t\t\t// Add columns for prefix and hash for pre auth keys, implementing\n\t\t\t\t// them with the same security model as api keys.\n\t\t\t\tID: \"202511011637-preauthkey-bcrypt\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\t// Check and add prefix column if it doesn't exist\n\t\t\t\t\tif !tx.Migrator().HasColumn(&types.PreAuthKey{}, \"prefix\") {\n\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.PreAuthKey{}, \"prefix\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding prefix column: %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check and add hash column if it doesn't exist\n\t\t\t\t\tif !tx.Migrator().HasColumn(&types.PreAuthKey{}, \"hash\") {\n\t\t\t\t\t\terr := tx.Migrator().AddColumn(&types.PreAuthKey{}, \"hash\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"adding hash column: %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create partial unique index to allow multiple legacy keys (NULL/empty prefix)\n\t\t\t\t\t// while enforcing uniqueness for new bcrypt-based keys\n\t\t\t\t\terr := tx.Exec(\"CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''\").Error\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"creating prefix index: %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{\n\t\t\t\tID: \"202511122344-remove-newline-index\",\n\t\t\t\tMigrate: func(tx *gorm.DB) error {\n\t\t\t\t\t// Reformat multi-line indexes to single-line for consistency","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L511-L547","documentation":"Thrown by migration '202511011637-preauthkey-bcrypt' when GORM cannot add the 'hash' column to pre_auth_keys (bcrypt hash storage for pre-auth keys). It is the second guarded AddColumn in the same migration; the wrapped error carries the true database-level cause. Because migrations execute in a startup transaction, this error prevents headscale from starting.","triggerScenarios":"Same startup migration path as the prefix column: pre_auth_keys.hash missing and AddColumn fails due to missing ALTER privilege, SQLite 'database is locked', full disk, or an unreadable/corrupt database file.","commonSituations":"Upgrading an old headscale database to the bcrypt pre-auth-key model; concurrent headscale instances; container running as a user that cannot write the SQLite file or its -wal/-shm siblings; PostgreSQL connection dropped mid-migration.","solutions":["Inspect the wrapped error text in the log for the DB-specific cause and address that directly","Verify filesystem permissions on the SQLite database directory (the WAL files must be writable too) or network connectivity/privileges for PostgreSQL","Stop all other headscale processes before upgrading so migrations run exclusively","If the error persists, restore the pre-migration backup and retry the upgrade in a controlled window"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure schema state is consistent before startup (SQLite example)\ndb, _ := sql.Open(\"sqlite\", file)\nvar n int\ndb.QueryRow(\"SELECT count(*) FROM pragma_table_info('pre_auth_keys') WHERE name IN ('prefix','hash')\").Scan(&n)\nif n != 0 && n != 2 {\n\tlog.Fatal(\"pre_auth_keys schema is half-migrated (prefix without hash or vice versa); restore from backup\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never kill headscale mid-startup; let migrations finish or restore the backup","Keep the SQLite file and its directory writable by the headscale process user (WAL files included)","Pin one headscale version across the fleet; mixed versions sharing a DB cause half-applied states"],"tags":["database","migration","gorm","bcrypt","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}