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

  1. Scroll up in the startup log: on Postgres the first error in the migration transaction is the cause; this message is often collateral
  2. Grant CREATE/INDEX privileges to the headscale DB role
  3. Ensure single-instance access to SQLite during upgrade
  4. 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

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


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