juanfont/headscale · critical
creating prefix index: %w
Error message
creating prefix index: %w
What it means
Migration '202511011637-preauthkey-bcrypt' fails to create the partial unique index idx_pre_auth_keys_prefix on pre_auth_keys(prefix). The most common cause is existing duplicate non-empty prefix values violating uniqueness; other causes are missing CREATE INDEX privilege, lock contention, or an index with the same name on a different table. IF NOT EXISTS protects only against the index already existing, not against constraint violations.
Source
Thrown at hscontrol/db/db.go:537
err := tx.Migrator().AddColumn(&types.PreAuthKey{}, "prefix")
if err != nil {
return fmt.Errorf("adding prefix column: %w", err)
}
}
// Check and add hash column if it doesn't exist
if !tx.Migrator().HasColumn(&types.PreAuthKey{}, "hash") {
err := tx.Migrator().AddColumn(&types.PreAuthKey{}, "hash")
if err != nil {
return fmt.Errorf("adding hash column: %w", err)
}
}
// Create partial unique index to allow multiple legacy keys (NULL/empty prefix)
// while enforcing uniqueness for new bcrypt-based keys
err := 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
if err != nil {
return fmt.Errorf("creating prefix index: %w", err)
}
return nil
},
Rollback: func(db *gorm.DB) error { return nil },
},
{
ID: "202511122344-remove-newline-index",
Migrate: func(tx *gorm.DB) error {
// Reformat multi-line indexes to single-line for consistency
// This migration drops and recreates the three user identity indexes
// to match the single-line format expected by schema validation
// Drop existing multi-line indexes
dropIndexes := []string{
`DROP INDEX IF EXISTS idx_provider_identifier`,
`DROP INDEX IF EXISTS idx_name_provider_identifier`,
`DROP INDEX IF EXISTS idx_name_no_provider_identifier`,View on GitHub (pinned to 565fd254d0)
Solutions
- Check the wrapped error - if it reports a duplicate key value, find offending rows: SELECT prefix, count(*) FROM pre_auth_keys WHERE prefix IS NOT NULL AND prefix != '' GROUP BY prefix HAVING count(*) > 1
- Deduplicate: keep the newest key per prefix and clear prefix/hash on the obsolete duplicates (or delete them) via a manual SQL session, then restart headscale
- If the cause is permissions/locking instead, grant CREATE INDEX or serialize access as with the AddColumn errors
- Restore from a pre-upgrade backup and re-run the migration cleanly if the data was hand-modified
Example fix
-- before: duplicates block the unique index SELECT prefix, count(*) FROM pre_auth_keys WHERE prefix IS NOT NULL AND prefix != '' GROUP BY prefix HAVING count(*) > 1; -- after: neutralize duplicates, then restart headscale UPDATE pre_auth_keys SET prefix = NULL, hash = NULL WHERE id NOT IN (SELECT max(id) FROM pre_auth_keys WHERE prefix IS NOT NULL GROUP BY prefix);
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: would the partial unique index succeed?
rows, err := db.Query(`SELECT prefix FROM pre_auth_keys
WHERE prefix IS NOT NULL AND prefix != ''
GROUP BY prefix HAVING count(*) > 1`)
if err != nil { return err }
if rows.Next() {
return errors.New("duplicate pre_auth_keys.prefix values would violate the unique index")
} Prevention
- Never hand-copy prefix values between pre-auth keys
- After any failed upgrade, inspect partial schema state before retrying
- Test upgrades on a copy of the production database first
When it happens
Trigger: Legacy pre_auth_keys rows that already contain a prefix column (e.g. from a partially applied earlier attempt) with duplicated prefix strings; CREATE UNIQUE INDEX ... WHERE prefix IS NOT NULL AND prefix != '' then aborts with a unique-constraint violation from the database.
Common situations: Re-running an upgrade after a crashed earlier migration attempt that added prefixes but not the index; hand-edited databases where prefixes were copied between keys; DB user without INDEX privilege on PostgreSQL.
Related errors
- adding description to pre_auth_keys: %w
- automigrating types.Route: %w
- automigrating types.PreAuthKey: %w
- automigrating types.Node: %w
- listing users: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/af0d49fb5b980424.
Report an issue: GitHub.