{"record":{"id":"209f9733a89aff26","repo":"juanfont/headscale","slug":"checking-if-table-s-exists-w","errorCode":null,"errorMessage":"checking if table %s exists: %w","messagePattern":"checking if table (.+?) exists: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/db/db.go","lineNumber":293,"sourceCode":"\t\t\t\t\t\t\"idx_users_deleted_at\",\n\t\t\t\t\t\t\"idx_provider_identifier\",\n\t\t\t\t\t\t\"idx_name_provider_identifier\",\n\t\t\t\t\t\t\"idx_name_no_provider_identifier\",\n\t\t\t\t\t\t\"idx_api_keys_prefix\",\n\t\t\t\t\t\t\"idx_policies_deleted_at\",\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, index := range indexesToDrop {\n\t\t\t\t\t\t_ = tx.Exec(\"DROP INDEX IF EXISTS \" + index).Error\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, table := range tablesToRename {\n\t\t\t\t\t\t// Check if table exists before renaming\n\t\t\t\t\t\tvar exists bool\n\n\t\t\t\t\t\terr := tx.Raw(\"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?\", table).Row().Scan(&exists)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"checking if table %s exists: %w\", table, err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif exists {\n\t\t\t\t\t\t\t// Drop old table if it exists from previous failed migration\n\t\t\t\t\t\t\t_ = tx.Exec(\"DROP TABLE IF EXISTS \" + table + \"_old\").Error\n\n\t\t\t\t\t\t\t// Rename current table to _old\n\t\t\t\t\t\t\terr := tx.Exec(\"ALTER TABLE \" + table + \" RENAME TO \" + table + \"_old\").Error\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"renaming table %s to %s_old: %w\", table, table, err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create new tables with correct schema\n\t\t\t\t\ttableCreationSQL := []string{\n\t\t\t\t\t\t`CREATE TABLE users(\n  id integer PRIMARY KEY AUTOINCREMENT,","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L275-L311","documentation":"The SQLite schema-recreation migration failed querying sqlite_master to test whether a table (users, pre_auth_keys, api_keys, nodes, policies) exists before renaming it. A bare SELECT from sqlite_master failing indicates a broken connection, corrupted database file, or a non-SQLite database reaching SQLite-only code.","triggerScenarios":"The Raw('SELECT COUNT(*) FROM sqlite_master ...').Row().Scan fails due to a dropped connection, a corrupted SQLite file (run PRAGMA integrity_check), or a misconfigured database type where the SQLite migration runs against another backend.","commonSituations":"Database file corrupted by power loss or forced kill during an earlier write; headscale.cfg pointing db.type to sqlite while the file is actually a Postgres dump or vice versa; disk errors.","solutions":["Run 'sqlite3 headscale.db \"PRAGMA integrity_check;\"' to detect corruption.","If corrupt, restore from backup - do not continue migrating a damaged file.","Verify the database configuration (type, path) matches the actual file.","If healthy, retry with exclusive access and check dmesg/logs for disk errors."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"-- Run before upgrading: confirm the file is a healthy SQLite database\nPRAGMA integrity_check;\n-- expect: ok","typeGuard":null,"tryCatchPattern":"if _, err := db.NewHeadscaleDatabase(cfg); err != nil {\n    if strings.Contains(err.Error(), \"checking if table\") {\n        // sqlite_master unreadable: corruption or wrong db.type config - restore backup\n    }\n}","preventionTips":["Run PRAGMA integrity_check on the SQLite file before major upgrades.","Ensure cfg db.type matches the actual database backend.","Use clean shutdowns (SIGTERM, not SIGKILL) to avoid corrupting the SQLite file."],"tags":["database","sqlite","migration","corruption","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}