gotify/server · critical

failed to create user: %w

Error message

failed to create user: %w

What it means

registerUser wraps a failure from DB.CreateUser while auto-provisioning a new account for a first-time OIDC login as 'failed to create user: %w' with HTTP 500. The identity is valid and unmatched, but the INSERT into the user store failed, so registration is aborted.

Source

Thrown at api/oidc.go:519

	return user, 0, nil
}

func (a *OIDCAPI) registerUser(username, oidcID string, hasAdminGroup bool) (*model.User, int, error) {
	if !a.AutoRegister {
		return nil, http.StatusForbidden, errors.New("user does not exist and auto-registration is disabled")
	}
	user := &model.User{
		Name:   username,
		Pass:   nil,
		OIDCID: &oidcID,
	}

	if len(a.GroupsAdmin) > 0 {
		user.Admin = hasAdminGroup
	}

	if err := a.DB.CreateUser(user); err != nil {
		return nil, http.StatusInternalServerError, fmt.Errorf("failed to create user: %w", err)
	}
	log.Info().Str("oidc_id", oidcID).Str("username", user.Name).Bool("admin", user.Admin).Msg("OIDC auto registration")
	if err := a.UserChangeNotifier.fireUserAdded(user.ID); err != nil {
		log.Error().Err(err).Uint("user_id", user.ID).Msg("Could not notify user change")
	}
	return user, 0, nil
}

func (a *OIDCAPI) createClient(name string, userID uint) (*model.Client, error) {
	elevatedUntil := time.Now().Add(model.DefaultElevationDuration)
	tokenPublic, tokenPrivate := generateClientToken()
	client := &model.Client{
		Name:                          name,
		Token:                         tokenPublic,
		UserID:                        userID,
		ElevatedUntil:                 &elevatedUntil,
		ExpiresAfterInactivitySeconds: auth.CookieMaxAge,
	}

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Check the wrapped driver error (duplicate key / constraint) in logs and address the specific constraint
  2. Run pending DB migrations for the users table
  3. Validate/sanitize the username claim length and characters at the IdP or in config
  4. Retry; if it was an insert race, the second login will link instead of register

Example fix

// before
username := fmt.Sprint(usernameRaw)
// after
username := fmt.Sprint(usernameRaw)
if len(username) > 64 { // keep within users.name column limit
    username = username[:64]
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate username length/charset before provisioning
if len(username) > 64 || strings.ContainsAny(username, "\x00") {
    return errors.New("username from IdP violates user table constraints")
}

Try / catch

user, status, err := resolveUser(...)
if err != nil && strings.Contains(err.Error(), "failed to create user") {
    var pgErr *pgconn.PgError
    if errors.As(err, &pgErr) && pgErr.Code == "23505" {
        // duplicate key race: retry login; it will link instead of register
    }
    http.Error(w, err.Error(), status)
    return
}

Prevention

When it happens

Trigger: First-ever OIDC login for an identity: no user by OIDC ID or username exists, registerUser builds a new model.User and a.DB.CreateUser(user) errors.

Common situations: Username from the claim violates DB constraints (too long, invalid chars, duplicate after race); users table out of migration; DB quota/disk full; unique index race between two simultaneous first logins.

Related errors


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/1322537e4629468c. Report an issue: GitHub.