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
- Inspect the wrapped error chain (errors.Unwrap / %w output) — the GORM/driver message names the real cause (constraint, lock, missing column).
- 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.
- For SQLite 'database is locked', reduce concurrent writers or check the busy_timeout setting in the sqlite config.
- 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
- Keep the binary version and database schema in sync; run migrations before serving traffic.
- For SQLite, set an adequate busy_timeout to avoid locked-database failures under concurrency.
- Monitor DB health (connections, disk space) so Save failures surface before key issuance is attempted.
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
- adding prefix column: %w
- opening database: %w
- saving API key to database: %w
- automigrating types.Route: %w
- automigrating types.PreAuthKey: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/7acda18edaaa9361.
Report an issue: GitHub.