{"record":{"id":"5070805f2ecd5bd5","repo":"juanfont/headscale","slug":"migration-failed-w","errorCode":null,"errorMessage":"migration failed: %w","messagePattern":"migration failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":994,"sourceCode":"\t\t\t`CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL`,\n\t\t\t`CREATE UNIQUE INDEX idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''`,\n\t\t\t`CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id)`,\n\t\t\t`CREATE UNIQUE INDEX idx_oauth_access_tokens_prefix ON oauth_access_tokens(prefix)`,\n\t\t}\n\n\t\tfor _, indexSQL := range indexes {\n\t\t\terr := tx.Exec(indexSQL).Error\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\terr = runMigrations(cfg.Database, dbConn, migrations)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"migration failed: %w\", err)\n\t}\n\n\t// Store the current version in the database after migrations succeed.\n\t// Dev builds skip this to preserve the stored version for the next\n\t// real versioned binary.\n\tcurrentVersion := types.GetVersionInfo().Version\n\tif !isDev(currentVersion) {\n\t\terr = setDatabaseVersion(dbConn, currentVersion)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"storing database version: %w\",\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\n\t// Validate that the schema ends up in the expected state.\n\t// This is currently only done on sqlite as squibble does not","sourceCodeStart":976,"sourceCodeEnd":1012,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L976-L1012","documentation":"Top-level wrapper around runMigrations(), which executes gormigrate migrations (and, on sqlite, the FK-disabled early phase and the final foreign-key check). Any failure in any individual migration, the InitSchema AutoMigrate step, or the sqlite constraint-violation sweep surfaces here. It is the generic 'schema did not reach the expected state' error for headscale startup.","triggerScenarios":"headscale start with a database that fails any migration: new migration SQL error (see error 400), MigrateTo('202501311657') failure, PRAGMA foreign_keys execution failure, or leftover foreign-key violations detected at the end of the sqlite migration run.","commonSituations":"Version downgrade (running an older binary against a DB migrated by a newer one — gormigrate rejects unknown migrations), interrupted migration leaving the schema_migrations table inconsistent, or a copy of the DB with a different collation/encoding.","solutions":["Read the wrapped error — it identifies which migration or phase failed; fix that specific cause (lock, permissions, corrupt table).","If you downgraded headscale, restore the binary version that matches the database's schema_migrations contents.","For an interrupted migration on sqlite, restore from backup/litestream replica rather than hand-editing schema_migrations.","Run with a fresh database to confirm the migration set itself is healthy, then compare against the failing DB.","Never reorder or edit committed migrations — migration order is immutable (see AGENTS.md database rules)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Treat startup migration failure as fatal unless the chained error is a\n// lock/busy condition. In an orchestrator:\n//   if strings.Contains(err.Error(), \"database is locked\") -> restart with backoff\n//   otherwise -> halt and page, restoring from backup.","preventionTips":["Back up the database before upgrading headscale.","Never run an older binary against a newer schema (downgrades are unsupported).","Verify disk space and write permissions before upgrades.","For sqlite, keep litestream in async replication mode so it does not hold write locks."],"tags":["database","migration","startup","gorm"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}