RocketChat/Rocket.Chat · error · MeteorError
error-duplicate-role-names-not-allowed
error-duplicate-role-names-not-allowed
Error message
Role name already exists
What it means
When a role update includes a new name, updateRole checks whether a different role already owns it: Roles.findOneByName(roleData.name) returning a document whose _id differs from the target throws MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists'). Renaming a role to its own current name is fine (same _id); colliding with any other role is not.
Solutions
- Pick a unique name, or refresh the roles list to see current names before renaming.
- If the goal is to merge two roles, move user assignments to the existing role and delete the redundant one instead of renaming onto it.
- Check Roles.findOneByName(newName) with an _id comparison before submitting the rename.
Example fix
// before
await updateRole(roleA._id, { name: 'support' }); // 'support' already owned by role B -> throws
// after
const other = await Roles.findOneByName('support');
if (other && other._id !== roleA._id) {
await updateRole(roleA._id, { name: 'support-tier2' }); // unique name
} else {
await updateRole(roleA._id, { name: 'support' });
} Defensive patterns
Strategy: validation
Validate before calling
if (roleData.name) {
const other = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });
if (other && other._id !== roleId) {
// name owned by another role; pick a different name before calling updateRole
}
} Try / catch
try {
await updateRole(roleId, roleData);
} catch (e: any) {
if (e?.error === 'error-duplicate-role-names-not-allowed') { surface(`Role name '${roleData.name}' is taken`); return; }
throw e;
} Prevention
- Refresh the roles list before renaming to see current names.
- For consolidation, migrate assignments to the existing role instead of renaming onto it.
- Treat role names as globally unique identifiers in validation schemas.
When it happens
Trigger: Renaming role A to a name already held by role B - including built-ins like 'moderator'; or two admins concurrently renaming different roles onto the same currently-free name, where the second update hits the duplicate.
Common situations: Consolidation scripts renaming roles to a canonical name that already exists; stale UI role lists that do not show a just-created role; import tooling that renames to standard names without checking.
Related errors
- error-duplicate-role-names-not-allowed
- error-action-not-allowed
- error-invalid-roleId
- error-invalid-scope
- error-invalid-scope
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/89989edf1cab4326.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/lib/roles/updateRole.ts:30
export const updateRole = async (
roleId: IRole['_id'],
roleData: Omit<IRole, '_id' | '_updatedAt'>,
options: UpdateRoleOptions = {},
): Promise<IRole> => {
const role = await Roles.findOneById(roleId);
if (!role) {
throw new MeteorError('error-invalid-roleId', 'This role does not exist');
}
if (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {
throw new MeteorError('error-role-protected', 'Role is protected');
}
if (roleData.name) {
const otherRole = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });
if (otherRole && otherRole._id !== role._id) {
throw new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');
}
} else {
roleData.name = role.name;
}
if (roleData.scope) {
if (!isValidRoleScope(roleData.scope)) {
throw new MeteorError('error-invalid-scope', 'Invalid scope');
}
} else {
roleData.scope = role.scope;
}
await Roles.updateById(roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa);
void notifyOnRoleChangedById(roleId);
if (options.broadcastUpdate) {View on GitHub (pinned to b2c16d5842)