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
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'.
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 f9d3ec372b)
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.
Example fix
// before
await updateRole(roleId, { name: newName });
// after
const clash = await Roles.findOneByName(newName, { projection: { _id: 1 } });
if (clash && clash._id !== roleId) {
throw new Error('name taken');
}
await updateRole(roleId, { name: newName }); Defensive patterns
Strategy: validation
Validate before calling
if (roleData.name) {
const clash = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });
if (clash && clash._id !== roleId) throw new Error('name taken');
} Type guard
const isRoleNameFreeFor = async (name: string, selfId: string) => {
const c = await Roles.findOneByName(name, { projection: { _id: 1 } });
return !c || c._id === selfId;
}; Try / catch
try { await updateRole(roleId, { name: newName }); }
catch (e) {
if (e?.code === 'error-duplicate-role-names-not-allowed') { /* pick unique name */ return; }
throw e;
} Prevention
- Pre-check role name uniqueness excluding the current _id.
- Surface existing role names in the rename form.
When it happens
Trigger: 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).
Common situations: Admin tries to rename a role to something already in use; import script normalizing names to existing ones.
Related errors
- error-duplicate-role-names-not-allowed
- error-invalid-scope
- error-invalid-roleId
- error-role-protected
- error-invalid-scope
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/89989edf1cab4326.
Report an issue: GitHub.