RocketChat/Rocket.Chat · error · MeteorError

error-invalid-user

error-invalid-user

Error message

Invalid user

What it means

Thrown by validateUserEditing() when Users.findOneById(userData._id) returns null — the user being updated no longer exists (or never did). It is the first check after validateUserData, so an update that passes permission checks still fails here if the target was deleted. This is a plain Meteor.Error with code error-invalid-user and no extra details object.

Source

Thrown at apps/meteor/server/lib/users/saveUser/validateUserEditing.ts:41

	return true;
};

/**
 * Validate permissions to edit user fields
 *
 * @param {string} userId
 * @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData
 */
export async function validateUserEditing(userId: IUser['_id'], userData: UpdateUserData): Promise<void> {
	const editingMyself = userData._id && userId === userData._id;

	const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info');
	const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password');
	const user = await Users.findOneById(userData._id);

	if (!user) {
		throw new MeteorError('error-invalid-user', 'Invalid user');
	}

	if (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) {
		throw new MeteorError('error-action-not-allowed', 'Assign roles is not allowed', {
			method: 'insertOrUpdateUser',
			action: 'Assign_role',
		});
	}

	if (!settings.get('Accounts_AllowUserProfileChange') && !canEditOtherUserInfo && !canEditOtherUserPassword) {
		throw new MeteorError('error-action-not-allowed', 'Edit user profile is not allowed', {
			method: 'insertOrUpdateUser',
			action: 'Update_user',
		});
	}

	if (
		isEditingField(user.username, userData.username) &&

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Refresh the id: call GET /api/v1/users.info?userId=... and only update when the user is found.
  2. Re-list the source of truth (users.list) and reconcile before retrying the update.
  3. If the account was deleted intentionally, create it instead of updating, or stop treating it as existing in your sync state.

Example fix

// before
await POST '/api/v1/users.update', { userId: staleId, data: { name: 'x' } }); // throws 'Invalid user'

// after
const info = await GET `/api/v1/users.info?userId=${staleId}`;
if (info?.userinfo?._id) {
  await POST '/api/v1/users.update', { userId: info.userinfo._id, data: { name: 'x' } });
} else {
  await POST '/api/v1/users.create', { username: 'recreated', email: 'a@b.c', password: 'pw' });
}
Defensive patterns

Strategy: validation

Validate before calling

const { userinfo } = await GET `/api/v1/users.info?userId=${encodeURIComponent(payload._id)}`;
if (!userinfo?._id) throw new Error('target user no longer exists');

Type guard

const isExistingUser = (u: { _id: string } | null | undefined): u is { _id: string } => !!u?._id;

Try / catch

catch (e) {
  if (e.error === 'error-invalid-user') {
    // refresh the user list / reconcile sync state; do NOT retry with the same id
  }
}

Prevention

When it happens

Trigger: users.update for an _id returned by a stale list (user deleted between listing and submitting); a wrong/hand-typed _id; a federated or merged account whose id changed; delete-and-recreate race with another admin.

Common situations: Long-lived admin screens or external directory syncs holding cached ids; automation that stores user ids across restarts; ids copied from a different workspace/environment (e.g. staging id used against production).

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/f0d03d41cdd468d7. Report an issue: GitHub.