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
- Inspect duplicates: SELECT id, email, create_date FROM user WHERE email = '<username>';
- Delete or merge the surplus rows by explicit id so exactly one remains.
- Add/repair a unique constraint on the email column to prevent recurrence.
- 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
- Monitor the user table for duplicate emails: SELECT email, COUNT(*) FROM user GROUP BY email HAVING COUNT(*) > 1;
- Keep a unique index on the email column after every upgrade.
- Never insert user rows via raw SQL; always go through the service layer.
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
- More than one user with username '{username}' found!
- 400
- User '{username}' not found
- Tenant not found!
- Failed to get skill folder ID
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/65450213e4d98f20.
Report an issue: GitHub.