infiniflow/ragflow · critical · AdminException

Exist more than 1 user: {username}!

Error message

Exist more than 1 user: {username}!

What it means

AdminException (HTTP 400) raised by UserMgr.delete_user (admin/server/services.py:106) when query_user_by_email returns more than one row. Because deletion acts on user_list[0] only, ambiguity is refused rather than guessing. Duplicate email rows indicate a broken uniqueness constraint or manual DB edits.

Source

Thrown at admin/server/services.py:106

            raise UserAlreadyExistsError(username)
        # Construct user info data
        user_info_dict = {
            "email": username,
            "nickname": "",  # ask user to edit it manually in settings.
            "password": decrypt(password),
            "login_channel": "password",
            "is_superuser": role == "admin",
        }
        return create_new_user(user_info_dict)

    @staticmethod
    def delete_user(username):
        # use email to delete
        user_list = UserService.query_user_by_email(username)
        if not user_list:
            raise UserNotFoundError(username)
        if len(user_list) > 1:
            raise AdminException(f"Exist more than 1 user: {username}!")
        usr = user_list[0]
        return delete_user_data(usr.id)

    @staticmethod
    def update_user_password(username, new_password) -> str:
        # use email to find user. check exist and unique.
        user_list = UserService.query_user_by_email(username)
        if not user_list:
            raise UserNotFoundError(username)
        elif len(user_list) > 1:
            raise AdminException(f"Exist more than 1 user: {username}!")
        # check new_password different from old.
        usr = user_list[0]
        psw = decrypt(new_password)
        # SSO-provisioned users (OIDC/OAuth) have no local password (usr.password is None):
        # skip the equality check, which would otherwise crash inside werkzeug's split().
        if usr.password and check_password_hash(usr.password, psw):
            return "Same password, no need to update!"

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Inspect duplicates: SELECT id, email, create_date FROM user WHERE email = '<username>';
  2. Delete or merge the surplus rows by explicit id so exactly one remains.
  3. Add/repair a unique constraint on the email column to prevent recurrence.
  4. Retry the admin delete call once the table is consistent.

Example fix

-- before: table has duplicates, admin delete returns 400
SELECT id, email FROM user WHERE email = 'user@example.com';
-- after: keep newest row, remove the rest
DELETE FROM user WHERE email = 'user@example.com' AND id NOT IN (
  SELECT id FROM (SELECT id FROM user WHERE email = 'user@example.com' ORDER BY create_date DESC LIMIT 1) t
);
ALTER TABLE user ADD UNIQUE KEY uq_user_email (email);
Defensive patterns

Strategy: validation

Validate before calling

from api.db.services import UserService

def assert_single_user(email: str) -> None:
    users = UserService.query_user_by_email(email)
    if len(users) != 1:
        raise RuntimeError(f'expected exactly 1 user for {email}, got {len(users)}')

Try / catch

from admin.server.exceptions import AdminException
try:
    UserMgr.delete_user(email)
except AdminException as e:
    if 'more than 1 user' in e.message.lower():
        raise RuntimeError(f'duplicate rows for {email}; run DB dedup first') from e
    raise

Prevention

When it happens

Trigger: Two or more user rows share the same email in the database; typically after imports, race-condition double-creates before a unique index existed, or direct SQL inserts bypassing the app's checks.

Common situations: Upgraded installations where the email unique index was never added; concurrent SSO first-login provisioning creating the same account twice; data restored/merged from another environment.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/65450213e4d98f20. Report an issue: GitHub.