juanfont/headscale · critical
creating oauth_clients index: %w
Error message
creating oauth_clients index: %w
What it means
The oauth_clients migration fails creating the unique index idx_oauth_clients_client_id on oauth_clients(client_id). The table was created moments before in the same transaction, so constraint violations are impossible; realistic causes are missing INDEX privilege, dialect issues, or - on Postgres - the transaction already aborted by an earlier failed statement, after which every Exec fails with 'current transaction is aborted'.
Source
Thrown at hscontrol/db/db.go:874
if !tx.Migrator().HasTable(&types.OAuthClient{}) {
err := tx.Exec(`CREATE TABLE oauth_clients(
id integer PRIMARY KEY AUTOINCREMENT,
client_id text,
secret_hash blob,
scopes text,
tags text,
description text,
user_id integer,
created_at datetime,
revoked datetime
)`).Error
if err != nil {
return fmt.Errorf("creating oauth_clients table: %w", err)
}
err = tx.Exec(`CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id)`).Error
if err != nil {
return fmt.Errorf("creating oauth_clients index: %w", err)
}
}
if !tx.Migrator().HasTable(&types.OAuthAccessToken{}) {
err := tx.Exec(`CREATE TABLE oauth_access_tokens(
id integer PRIMARY KEY AUTOINCREMENT,
prefix text,
hash blob,
client_id text,
scopes text,
tags text,
expiration datetime,
created_at datetime
)`).Error
if err != nil {
return fmt.Errorf("creating oauth_access_tokens table: %w", err)
}
View on GitHub (pinned to 565fd254d0)
Solutions
- Scroll up in the startup log: on Postgres the first error in the migration transaction is the cause; this message is often collateral
- Grant CREATE/INDEX privileges to the headscale DB role
- Ensure single-instance access to SQLite during upgrade
- After fixing the root cause, restart - the transaction replays and the HasTable/HasColumn guards keep it idempotent
Defensive patterns
Strategy: try-catch
Validate before calling
// Postgres: confirm CREATE privilege before upgrading
var canCreate bool
db.QueryRow("SELECT has_schema_privilege(current_user, current_schema(), 'CREATE')").Scan(&canCreate)
if !canCreate {
log.Fatal("role lacks CREATE on schema; index creation will fail")
} Try / catch
// When wrapping startup: report the whole migration chain, not just the last error
if err := runMigrations(db); err != nil {
log.Error().Err(errors.Unwrap(err)).Msg("root-cause error (this index error may be collateral of an earlier statement)")
os.Exit(1)
} Prevention
- On Postgres always read the FIRST error in a failed migration transaction; later statements fail with 'transaction is aborted'
- Grant INDEX/CREATE privileges before upgrading
- Avoid connection poolers that break transaction semantics during migration
When it happens
Trigger: Postgres aborting the transaction after a prior failed statement (this index error is collateral); CREATE INDEX revoked from the role; SQLite lock escalation failure.
Common situations: Least-privilege DB roles; diagnosing only the last error in a chain instead of the first failed statement of the migration.
Related errors
- creating oauth_access_tokens index: %w
- fetching routes: %w
- adding prefix column: %w
- creating prefix index: %w
- dropping index: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/8d7af664d00abd45.
Report an issue: GitHub.