{"record":{"id":"0db5590703d0c5ac","repo":"RocketChat/Rocket.Chat","slug":"error-duplicate-role-names-not-allowed","errorCode":"error-duplicate-role-names-not-allowed","errorMessage":"Role name already exists","messagePattern":"Role name already exists","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/api/roles.ts","lineNumber":127,"sourceCode":"\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t400: validateBadRequestErrorResponse,\n\t\t},\n\t},\n\tasync function action() {\n\t\tif (!License.hasModule('custom-roles')) {\n\t\t\tthrow new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');\n\t\t}\n\n\t\tconst { userId } = this;\n\n\t\tif (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {\n\t\t\tthrow new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed');\n\t\t}\n\n\t\tconst { name, scope, description, mandatory2fa } = this.bodyParams;\n\n\t\tif (await Roles.findOneByIdOrName(name)) {\n\t\t\tthrow new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t\t}\n\n\t\tconst roleData = {\n\t\t\tdescription: description || '',\n\t\t\t...(mandatory2fa !== undefined && { mandatory2fa }),\n\t\t\tname,\n\t\t\tscope: scope || 'Users',\n\t\t\tprotected: false,\n\t\t};\n\n\t\tconst options = {\n\t\t\tbroadcastUpdate: settings.get<boolean>('UI_DisplayRoles'),\n\t\t};\n\n\t\tconst role = await insertRoleAsync(roleData, options);\n\n\t\treturn API.v1.success({ role });\n\t},","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/api/roles.ts#L109-L145","documentation":"Thrown by POST roles.create when Roles.findOneByIdOrName(name) returns an existing role. Role names (and IDs) must be unique; passing a name that collides with an existing role (by _id or by name) is rejected. Returns Meteor.Error error-duplicate-role-names-not-allowed.","triggerScenarios":"Calling POST /v1/roles.create with a body whose name matches an existing role's name or _id. Happens on retry after a partial create, on duplicate submissions, or when re-importing a role set.","commonSituations":"Re-running a setup script that creates roles; name collision with a built-in role (admin, user, livechat-agent); case sensitivity surprises (lookup is by DB exact match).","solutions":["Check for an existing role via GET /v1/roles.list before creating, and reuse it if present.","Choose a unique role name.","If the duplicate was accidental, delete the existing role first (DELETE /v1/roles.delete) if it is not protected.","Make setup scripts idempotent: skip creation when the role already exists."],"exampleFix":"// before: unconditional create\nawait POST('/v1/roles.create', { name: 'support-agent' });\n\n// after: idempotent create\nconst existing = (await GET('/v1/roles.list')).roles.find(r => r.name === 'support-agent');\nif (!existing) {\n  await POST('/v1/roles.create', { name: 'support-agent' });\n}","handlingStrategy":"validation","validationCode":"// Idempotency check before create\nconst { roles } = await api.get('roles.list');\nif (roles.some(r => r.name === body.name || r._id === body.name)) {\n  throw new Error(`Role '${body.name}' already exists`);\n}\nawait api.post('roles.create', body);","typeGuard":null,"tryCatchPattern":"try {\n  await api.post('roles.create', body);\n} catch (e) {\n  if (isMeteorError(e, 'error-duplicate-role-names-not-allowed')) {\n    // role already exists; fetch and reuse it\n    return api.get('roles.list').then(r => r.roles.find(x => x.name === body.name));\n  }\n  throw e;\n}","preventionTips":["Make role-provisioning scripts idempotent (lookup-then-create).","Avoid reusing built-in role names (admin, user, livechat-agent, etc.)."],"tags":["roles","duplicate","rest-api","validation"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}