RocketChat/Rocket.Chat · warning · Meteor.Error

error-user-not-in-role

error-user-not-in-role

Error message

User is not in this role

What it means

Thrown by POST roles.removeUserFromRole when hasAnyRoleAsync(user._id, [role._id], scope) is false. The user and role both exist, but the user does not currently hold the role in the given scope (or globally if scope omitted). Returns a structured Meteor.Error('error-user-not-in-role', ...).

Source

Thrown at apps/meteor/server/api/v1/roles.ts:297

			if (!roleId) {
				return API.v1.failure('error-invalid-role-properties');
			}

			const user = await Users.findOneByUsername(username);

			if (!user) {
				throw new Meteor.Error('error-invalid-user', 'There is no user with this username');
			}

			const role = await Roles.findOneById(roleId);

			if (!role) {
				throw new Meteor.Error('error-invalid-roleId', 'This role does not exist');
			}

			if (!(await hasAnyRoleAsync(user._id, [role._id], scope))) {
				throw new Meteor.Error('error-user-not-in-role', 'User is not in this role');
			}

			if (role._id === 'admin') {
				const adminCount = await Roles.countUsersInRole('admin');
				if (adminCount === 1) {
					throw new Meteor.Error('error-admin-required', 'You need to have at least one admin');
				}
			}

			await removeUserFromRolesAsync(user._id, [role._id], scope);

			if (settings.get('UI_DisplayRoles')) {
				void api.broadcast('user.roleUpdate', {
					type: 'removed',
					_id: role._id,
					u: {
						_id: user._id,
						username: user.username,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Treat the error as success for idempotent unassign workflows (user is already not in the role).
  2. Match the scope exactly: if the grant was room-scoped, pass the same roomId/scope on removal.
  3. Verify current membership via roles.getUsersInRole before removing.

Example fix

// before
await fetch('/api/v1/roles.removeUserFromRole', { method:'POST', body: JSON.stringify({ roleId, username }) });

// after - idempotent unassign
try {
  await fetch('/api/v1/roles.removeUserFromRole', { method:'POST', body: JSON.stringify({ roleId, username }) });
} catch (e) {
  if (e.error === 'error-user-not-in-role') return; // already in desired state
  throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await fetch('/api/v1/roles.removeUserFromRole', {method:'POST',body:JSON.stringify({roleId,username,scope})}).then(r=>r.json());
} catch (e) {
  if (e.error === 'error-user-not-in-role') return; // idempotent success
  throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/roles.removeUserFromRole for a user+role+scope combination the user does not have; re-running an unassign; scope mismatch (e.g. room-scoped vs global).

Common situations: Idempotent cleanup script re-removes roles; UI shows stale membership; role grant was room-scoped but removal sent without the matching scope.

Related errors


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