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

  1. Fix the wrapped cause: make the DB writable, unlock it, grant ALTER, free space
  2. Manual fallback for drifted schemas: ALTER TABLE pre_auth_keys ADD COLUMN revoked datetime NULL; restart - HasColumn then skips AddColumn
  3. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/9eaf9d97fd178b92. Report an issue: GitHub.