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

  1. Pick a unique name, or refresh the roles list to see current names before renaming.
  2. 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.
  3. 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

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


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)