juanfont/headscale · critical
adding revoked to pre_auth_keys: %w
Error message
adding revoked to pre_auth_keys: %w
What it means
Migration '202606201200-pre-auth-key-revoked' fails adding the revoked timestamp column to pre_auth_keys, which backs the v2 API's soft-revoke (revoked = now) semantics. Same AddColumn failure class: DDL privileges, locks, disk space, or a read-only database.
Source
Thrown at hscontrol/db/db.go:826
if err != nil {
return fmt.Errorf("adding description to pre_auth_keys: %w", err)
}
}
return nil
},
Rollback: func(db *gorm.DB) error { return nil },
},
{
// Add a revoked timestamp to pre-auth keys. The v2 API's DELETE
// soft-revokes a key (set revoked = now) rather than destroying
// it; the row is reaped later by the background collector.
ID: "202606201200-pre-auth-key-revoked",
Migrate: func(tx *gorm.DB) error {
if !tx.Migrator().HasColumn(&types.PreAuthKey{}, "revoked") {
err := tx.Migrator().AddColumn(&types.PreAuthKey{}, "revoked")
if err != nil {
return fmt.Errorf("adding revoked to pre_auth_keys: %w", err)
}
}
return nil
},
Rollback: func(db *gorm.DB) error { return nil },
},
{
// Add the OAuth client + access token tables backing the v2 API's
// OAuth client-credentials flow. They mirror the api_keys /
// pre_auth_keys security model: a public id/prefix plus an Argon2id
// hash of the secret.
//
// SQLite uses explicit DDL that matches schema.sql byte-for-byte
// (the squibble digest is the SQLite source of truth). Postgres,
// which has no digest and rejects SQLite-isms like AUTOINCREMENT,
// uses dialect-aware AutoMigrate, mirroring InitSchema's fresh-DB
// table creation so an existing Postgres deployment can upgrade.View on GitHub (pinned to 565fd254d0)
Solutions
- Fix the wrapped cause: make the DB writable, unlock it, grant ALTER, free space
- Manual fallback for drifted schemas: ALTER TABLE pre_auth_keys ADD COLUMN revoked datetime NULL; restart - HasColumn then skips AddColumn
- Verify soft-revoke works after upgrade by revoking a test key via the v2 API and checking revoked is set
Defensive patterns
Strategy: validation
Validate before calling
// Confirm the database accepts DDL before the upgrade window
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS _probe(id integer)"); err != nil {
log.Fatalf("cannot execute DDL (locked/readonly/no privilege): %v", err)
}
db.Exec("DROP TABLE IF EXISTS _probe") Prevention
- Back up before upgrading to soft-revoke-capable versions
- After upgrade, smoke-test: create and revoke a pre-auth key via the v2 API
When it happens
Trigger: Schema lacking pre_auth_keys.revoked while the guarded AddColumn cannot execute due to environment problems rather than the guard logic itself.
Common situations: Version upgrades introducing soft-revocation; locked SQLite files; read-only database mounts (e.g. container filesystem mounted ro).
Related errors
- adding description to pre_auth_keys: %w
- adding column types.Node: %w
- dropping routes table: %w
- renaming table %s to %s_old: %w
- creating new table: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9eaf9d97fd178b92.
Report an issue: GitHub.