juanfont/headscale · error

creating key in database: %w

Error message

creating key in database: %w

What it means

GORM failed to persist a newly created pre-auth key during CreatePreAuthKeyCtx. The transaction's tx.Save on the PreAuthKey record returned an error, which is wrapped with this message. This is a database-layer failure, not a validation failure: the key string was already generated and hashed, but the row could not be written.

Source

Thrown at hscontrol/db/preauth_keys.go:130

	hash, err := bcrypt.GenerateFromPassword([]byte(toBeHashed), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	key := types.PreAuthKey{
		UserID:     userID, // nil for system-created keys, or "created by" for tagged keys
		User:       user,   // nil for system-created keys
		Reusable:   reusable,
		Ephemeral:  ephemeral,
		CreatedAt:  &now,
		Expiration: expiration,
		Tags:       aclTags, // empty for user-owned keys
		Prefix:     prefix,  // Store prefix
		Hash:       hash,    // Store hash
	}

	if err := tx.Save(&key).Error; err != nil { //nolint:noinlineerr
		return nil, fmt.Errorf("creating key in database: %w", err)
	}

	return &types.PreAuthKeyNew{
		ID:         key.ID,
		Key:        keyStr,
		Reusable:   key.Reusable,
		Ephemeral:  key.Ephemeral,
		Tags:       key.Tags,
		Expiration: key.Expiration,
		CreatedAt:  key.CreatedAt,
		User:       key.User,
	}, nil
}

// SetPreAuthKeyDescription sets the free-text description on a pre-auth key.
// The v2 keys API sets it after creation rather than threading it through the
// many-armed CreatePreAuthKey signature shared by every other caller.
func (hsdb *HSDatabase) SetPreAuthKeyDescription(id uint64, description string) error {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Inspect the wrapped error chain (errors.Unwrap / %w output) — the GORM/driver message names the real cause (constraint, lock, missing column).
  2. If the error mentions a missing column (prefix, hash), run migrations: ensure hscontrol/db migration code ran (headscale starts them automatically) and your binary version matches the schema.
  3. For SQLite 'database is locked', reduce concurrent writers or check the busy_timeout setting in the sqlite config.
  4. Verify database connectivity and permissions (path writable, Postgres up) and retry the create operation once the cause is fixed.
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil {
    var key *types.PreAuthKeyNew
    _ = key
    if errors.Is(err, gorm.ErrDuplicatedKey) {
        // prefix collision (astronomically unlikely) or retry storm; regenerate and retry once
    }
    log.Error().Err(err).Msg("pre-auth key creation failed")
    return fmt.Errorf("create preauth key: %w", err)
}

Prevention

When it happens

Trigger: Calling the pre-auth key creation path (db.CreatePreAuthKeyCtx / the CLI 'preauthkeys create' command / the gRPC CreatePreAuthKey API) when the underlying tx.Save fails: DB connection lost mid-transaction, unique-constraint violation on prefix, disk full, SQLite database locked, or a schema/migration mismatch where the prefix or hash column does not exist.

Common situations: Running an older binary against a newer schema (missing prefix/hash columns added by a migration), SQLite 'database is locked' under concurrent writers, PostgreSQL connection dropped, or a restored/corrupted database file. Note the caller must not retry key generation blindly: the key string was returned nowhere, but the row may or may not exist.

Related errors


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