RocketChat/Rocket.Chat · warning · Meteor.Error

error-role-in-use

error-role-in-use

Error message

Cannot delete role because it's in use

What it means

Thrown by POST roles.delete when Roles.countUsersInRole(role._id) is greater than zero. The role is non-protected but still has users assigned, so deletion is blocked to avoid orphaned assignments. Returns a structured Meteor.Error('error-role-in-use', ...).

Source

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

				401: validateUnauthorizedErrorResponse,
				403: validateForbiddenErrorResponse,
			},
		},
		async function action() {
			const { bodyParams } = this;

			const role = await Roles.findOneByIdOrName(bodyParams.roleId);

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

			if (role.protected) {
				throw new Meteor.Error('error-role-protected', 'Cannot delete a protected role');
			}

			if ((await Roles.countUsersInRole(role._id)) > 0) {
				throw new Meteor.Error('error-role-in-use', "Cannot delete role because it's in use");
			}

			await Roles.removeById(role._id);

			void notifyOnRoleChanged(role, 'removed');

			return API.v1.success();
		},
	)
	.post(
		'roles.removeUserFromRole',
		{
			authRequired: true,
			permissionsRequired: ['access-permissions'],
			body: isRoleRemoveUserFromRoleProps,
			response: {
				200: ajv.compile<{ role: IRole }>({
					type: 'object',

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Remove all users from the role first via roles.removeUserFromRole (per user/scope), then retry the delete.
  2. Use GET /api/v1/roles.getUsersInRole to enumerate current grantees before unassigning.
  3. Confirm count is truly zero with countUsersInRole before the delete call.

Example fix

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

// after - unassign all users, then delete
const { users } = await fetch(`/api/v1/roles.getUsersInRole?role=${roleId}`).then(r=>r.json());
for (const u of users) {
  await fetch('/api/v1/roles.removeUserFromRole', { method:'POST', body: JSON.stringify({ roleId, username: u.username }) });
}
await fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the role has no grantees before deleting
const { total } = await fetch(`/api/v1/roles.getUsersInRole?role=${encodeURIComponent(roleId)}`).then(r => r.json());
if (total > 0) throw new Error(`Role still has ${total} user(s); unassign first`);
await fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });

Try / catch

try {
  await fetch('/api/v1/roles.delete', {method:'POST',body:JSON.stringify({roleId})}).then(r=>r.json());
} catch (e) {
  if (e.error === 'error-role-in-use') { /* enumerate + unassign, then retry */ }
}

Prevention

When it happens

Trigger: POST /api/v1/roles.delete for a non-protected role that still has at least one user granted it (globally or in any scope).

Common situations: Trying to clean up a custom role before removing all grantees; bulk permission refactor that forgets the unassign step.

Related errors


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