{"record":{"id":"c4898f7d362d5c96","repo":"RocketChat/Rocket.Chat","slug":"error-role-in-use","errorCode":"error-role-in-use","errorMessage":"Cannot delete role because it's in use","messagePattern":"Cannot delete role because it's in use","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/api/v1/roles.ts","lineNumber":247,"sourceCode":"\t\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t\t403: validateForbiddenErrorResponse,\n\t\t\t},\n\t\t},\n\t\tasync function action() {\n\t\t\tconst { bodyParams } = this;\n\n\t\t\tconst role = await Roles.findOneByIdOrName(bodyParams.roleId);\n\n\t\t\tif (!role) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-roleId', 'This role does not exist');\n\t\t\t}\n\n\t\t\tif (role.protected) {\n\t\t\t\tthrow new Meteor.Error('error-role-protected', 'Cannot delete a protected role');\n\t\t\t}\n\n\t\t\tif ((await Roles.countUsersInRole(role._id)) > 0) {\n\t\t\t\tthrow new Meteor.Error('error-role-in-use', \"Cannot delete role because it's in use\");\n\t\t\t}\n\n\t\t\tawait Roles.removeById(role._id);\n\n\t\t\tvoid notifyOnRoleChanged(role, 'removed');\n\n\t\t\treturn API.v1.success();\n\t\t},\n\t)\n\t.post(\n\t\t'roles.removeUserFromRole',\n\t\t{\n\t\t\tauthRequired: true,\n\t\t\tpermissionsRequired: ['access-permissions'],\n\t\t\tbody: isRoleRemoveUserFromRoleProps,\n\t\t\tresponse: {\n\t\t\t\t200: ajv.compile<{ role: IRole }>({\n\t\t\t\t\ttype: 'object',","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/roles.ts#L229-L265","documentation":"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', ...).","triggerScenarios":"POST /api/v1/roles.delete for a non-protected role that still has at least one user granted it (globally or in any scope).","commonSituations":"Trying to clean up a custom role before removing all grantees; bulk permission refactor that forgets the unassign step.","solutions":["Remove all users from the role first via roles.removeUserFromRole (per user/scope), then retry the delete.","Use GET /api/v1/roles.getUsersInRole to enumerate current grantees before unassigning.","Confirm count is truly zero with countUsersInRole before the delete call."],"exampleFix":"// before\nawait fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });\n\n// after - unassign all users, then delete\nconst { users } = await fetch(`/api/v1/roles.getUsersInRole?role=${roleId}`).then(r=>r.json());\nfor (const u of users) {\n  await fetch('/api/v1/roles.removeUserFromRole', { method:'POST', body: JSON.stringify({ roleId, username: u.username }) });\n}\nawait fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });","handlingStrategy":"validation","validationCode":"// Ensure the role has no grantees before deleting\nconst { total } = await fetch(`/api/v1/roles.getUsersInRole?role=${encodeURIComponent(roleId)}`).then(r => r.json());\nif (total > 0) throw new Error(`Role still has ${total} user(s); unassign first`);\nawait fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });","typeGuard":null,"tryCatchPattern":"try {\n  await fetch('/api/v1/roles.delete', {method:'POST',body:JSON.stringify({roleId})}).then(r=>r.json());\n} catch (e) {\n  if (e.error === 'error-role-in-use') { /* enumerate + unassign, then retry */ }\n}","preventionTips":["Always unassign all users before deleting a role.","Build a single 'purge role' routine that unassigns then deletes."],"tags":["roles","rest-api","business-rule","delete","conflict"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}