{"record":{"id":"89989edf1cab4326","repo":"RocketChat/Rocket.Chat","slug":"error-duplicate-role-names-not-allowed-89989e","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/updateRole.ts","lineNumber":30,"sourceCode":"export const updateRole = async (\n\troleId: IRole['_id'],\n\troleData: Omit<IRole, '_id' | '_updatedAt'>,\n\toptions: UpdateRoleOptions = {},\n): Promise<IRole> => {\n\tconst role = await Roles.findOneById(roleId);\n\n\tif (!role) {\n\t\tthrow new MeteorError('error-invalid-roleId', 'This role does not exist');\n\t}\n\n\tif (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {\n\t\tthrow new MeteorError('error-role-protected', 'Role is protected');\n\t}\n\n\tif (roleData.name) {\n\t\tconst otherRole = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });\n\t\tif (otherRole && otherRole._id !== role._id) {\n\t\t\tthrow new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t\t}\n\t} else {\n\t\troleData.name = role.name;\n\t}\n\n\tif (roleData.scope) {\n\t\tif (!isValidRoleScope(roleData.scope)) {\n\t\t\tthrow new MeteorError('error-invalid-scope', 'Invalid scope');\n\t\t}\n\t} else {\n\t\troleData.scope = role.scope;\n\t}\n\n\tawait Roles.updateById(roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa);\n\n\tvoid notifyOnRoleChangedById(roleId);\n\n\tif (options.broadcastUpdate) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/lib/roles/updateRole.ts#L12-L48","documentation":"Thrown by updateRole in updateRole.ts:30 during a rename: another role already uses the requested name. findOneByName excludes nothing, so the code manually compares otherRole._id !== role._id. MeteorError code 'error-duplicate-role-names-not-allowed'.","triggerScenarios":"Renaming role A to the name of role B; or submitting the form with a name collision. Setting roleData.name to its own current name does not fire (different _id check passes).","commonSituations":"Admin tries to rename a role to something already in use; import script normalizing names to existing ones.","solutions":["Pick a role name not used by any other role.","Pre-check Roles.findOneByName(roleData.name) and ensure _id matches the role being edited.","Catch MeteorError 'error-duplicate-role-names-not-allowed' and prompt for a unique name."],"exampleFix":"// before\nawait updateRole(roleId, { name: newName });\n\n// after\nconst clash = await Roles.findOneByName(newName, { projection: { _id: 1 } });\nif (clash && clash._id !== roleId) {\n  throw new Error('name taken');\n}\nawait updateRole(roleId, { name: newName });","handlingStrategy":"validation","validationCode":"if (roleData.name) {\n  const clash = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });\n  if (clash && clash._id !== roleId) throw new Error('name taken');\n}","typeGuard":"const isRoleNameFreeFor = async (name: string, selfId: string) => {\n  const c = await Roles.findOneByName(name, { projection: { _id: 1 } });\n  return !c || c._id === selfId;\n};","tryCatchPattern":"try { await updateRole(roleId, { name: newName }); }\ncatch (e) {\n  if (e?.code === 'error-duplicate-role-names-not-allowed') { /* pick unique name */ return; }\n  throw e;\n}","preventionTips":["Pre-check role name uniqueness excluding the current _id.","Surface existing role names in the rename 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"}