RocketChat/Rocket.Chat · warning · Meteor.Error

error-user-already-in-role

error-user-already-in-role

Error message

User already in role

What it means

Thrown by POST /api/v1/roles.addUserToRole when hasRoleAsync(user._id, role._id, roomId) is already true — the target user already holds that role, globally or in the given room scope. The endpoint deliberately rejects double-assignment instead of being idempotent, so retries of a partially-succeeded flow or scripts that re-add existing roles hit this.

Solutions

  1. Make callers idempotent: catch error-user-already-in-role and treat it as success
  2. Pre-check with roles.getUsersInRole (needs access-permissions) or users.info before assigning
  3. Fix the upstream retry logic if the same assignment is being pushed repeatedly

Example fix

// before
await sdk.post('roles.addUserToRole', { roleId, username });

// after (idempotent assign)
try {
  await sdk.post('roles.addUserToRole', { roleId, username });
} catch (e) {
  if (e.error !== 'error-user-already-in-role') throw e;
  // already assigned — nothing to do
}
Defensive patterns

Strategy: try-catch

Validate before calling

// optional pre-check (global scope only — room-scoped grants are not in user.roles)
const { user } = await sdk.get('users.info', { username });
if (!roomId && user.roles?.includes(roleName)) return { alreadyAssigned: true };

Try / catch

wrap roles.addUserToRole in a catch that treats e.error === 'error-user-already-in-role' as success (idempotent assign); rethrow everything else.

Prevention

When it happens

Trigger: POST roles.addUserToRole { username, roleId, roomId? } where the user already has the role: re-running an onboarding script; retrying after a network blip where the first attempt actually committed; adding a role in a room scope the user already holds there.

Common situations: Idempotency-unaware provisioning scripts (SCIM/LDAP sync) run twice; UI double-submit; migration re-applying role grants from a snapshot.

Related errors


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

Appendix: source

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

				400: validateBadRequestErrorResponse,
				401: validateUnauthorizedErrorResponse,
			},
		},
		async function action() {
			const user = await getUserFromParams(this.bodyParams);
			const { roleId, roomId } = this.bodyParams;

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

			const role = await Roles.findOneById(roleId);
			if (!role) {
				return API.v1.failure('error-role-not-found', 'Role not found');
			}

			if (await hasRoleAsync(user._id, role._id, roomId)) {
				throw new Meteor.Error('error-user-already-in-role', 'User already in role');
			}

			await addUserToRole(this.userId, role._id, user.username, roomId);

			return API.v1.success({
				role,
			});
		},
	)
	.get(
		'roles.getUsersInRole',
		{
			authRequired: true,
			permissionsRequired: ['access-permissions'],
			query: isRolesGetUsersInRoleProps,
			response: {
				200: ajv.compile<{ users: IUserInRole[]; total: number }>({
					type: 'object',

View on GitHub (pinned to b2c16d5842)