{"record":{"id":"a14be4c13dca2180","repo":"RocketChat/Rocket.Chat","slug":"error-duplicate-role-names-not-allowed-a14be4","errorCode":"error-duplicate-role-names-not-allowed","errorMessage":"Role name already exists","messagePattern":"Role name already exists","errorType":"exception","errorClass":"MeteorError","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/roles/insertRole.ts","lineNumber":16,"sourceCode":"import { api, MeteorError } from '@rocket.chat/core-services';\nimport type { IRole } from '@rocket.chat/core-typings';\nimport { Roles } from '@rocket.chat/models';\n\nimport { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';\nimport { notifyOnRoleChanged } from '../../../../server/lib/notifyListener';\n\ntype InsertRoleOptions = {\n\tbroadcastUpdate?: boolean;\n};\n\nexport const insertRoleAsync = async (roleData: Omit<IRole, '_id' | '_updatedAt'>, options: InsertRoleOptions = {}): Promise<IRole> => {\n\tconst { name, scope, description, mandatory2fa } = roleData;\n\n\tif (await Roles.findOneByName(name)) {\n\t\tthrow new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t}\n\n\tif (!isValidRoleScope(scope)) {\n\t\tthrow new MeteorError('error-invalid-scope', 'Invalid scope');\n\t}\n\n\tconst role = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);\n\n\tvoid notifyOnRoleChanged(role);\n\n\tif (options.broadcastUpdate) {\n\t\tvoid api.broadcast('user.roleUpdate', {\n\t\t\ttype: 'changed',\n\t\t\t_id: role._id,\n\t\t});\n\t}\n\n\treturn role;","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/lib/roles/insertRole.ts#L1-L34","documentation":"Thrown by insertRoleAsync in insertRole.ts:16 when Roles.findOneByName(name) returns a role — i.e. a role with that name already exists. Uses MeteorError with code 'error-duplicate-role-names-not-allowed'. Role names are globally unique in Rocket.Chat.","triggerScenarios":"Calling insertRoleAsync with a name that matches any existing role (case-sensitive findOneByName). Happens on role creation via UI, REST, or seeding scripts.","commonSituations":"Re-running a setup/seeding script; admin manually creates a role that ships as default; migration recreates built-in roles.","solutions":["Check Roles.findOneByName(name) first and skip / update instead of insert.","Catch MeteorError code 'error-duplicate-role-names-not-allowed' and treat as idempotent.","Use a unique name or call updateRole on the existing _id."],"exampleFix":"// before\nawait insertRoleAsync({ name, scope, description });\n\n// after\nif (await Roles.findOneByName(name)) {\n  // already exists; treat as success or update\n  return Roles.findOneByName(name);\n}\nawait insertRoleAsync({ name, scope, description });","handlingStrategy":"validation","validationCode":"if (await Roles.findOneByName(name)) { /* skip or update instead */ return; }","typeGuard":"const isRoleNameFree = async (name: string) => !(await Roles.findOneByName(name));","tryCatchPattern":"try { await insertRoleAsync(roleData); }\ncatch (e) {\n  if (e?.code === 'error-duplicate-role-names-not-allowed') return;\n  throw e;\n}","preventionTips":["Make role-seeding scripts idempotent.","Pre-check name uniqueness in the role form."],"tags":["roles","uniqueness","meteor-error"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}