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

insertRoleAsync (the Enterprise role-creation helper) refuses to create a role whose name already exists: if Roles.findOneByName(name) finds a document it throws MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists'). Role names are the globally unique key used for permission assignment, so duplicates are rejected at the application layer before any write.

Solutions

  1. Make provisioning idempotent: look the role up by name first and reuse the existing one instead of erroring.
  2. Choose a name that does not collide with built-in or existing roles.
  3. On a race, catch the error and re-fetch - the role now exists and can be used.

Example fix

// before
await insertRoleAsync({ name: 'auditor', scope: 'Users', description: 'Read-only audits' }); // throws if 'auditor' exists

// after
const existing = await Roles.findOneByName('auditor');
if (existing) return existing;
return insertRoleAsync({ name: 'auditor', scope: 'Users', description: 'Read-only audits' });
Defensive patterns

Strategy: validation

Validate before calling

const existing = await Roles.findOneByName(name);
if (existing) {
	// reuse the existing role instead of creating a duplicate
}

Try / catch

try {
	await insertRoleAsync(roleData);
} catch (e: any) {
	if (e?.error === 'error-duplicate-role-names-not-allowed') { return Roles.findOneByName(roleData.name); } // created concurrently; reuse
	throw e;
}

Prevention

When it happens

Trigger: Calling insertRoleAsync (backing role creation for the Permissions admin surface) with a name that already exists - including built-ins such as admin, moderator, livechat-agent, guest, bot, and any previously created custom role.

Common situations: Re-running a seeding/provisioning script that creates the same custom role twice; creating a role whose name collides with a built-in; two concurrent requests racing to create the same role where the loser hits the duplicate check.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/a14be4c13dca2180. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/ee/server/lib/roles/insertRole.ts:16

import { api, MeteorError } from '@rocket.chat/core-services';
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';

import { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';
import { notifyOnRoleChanged } from '../../../../server/lib/notifyListener';

type InsertRoleOptions = {
	broadcastUpdate?: boolean;
};

export const insertRoleAsync = async (roleData: Omit<IRole, '_id' | '_updatedAt'>, options: InsertRoleOptions = {}): Promise<IRole> => {
	const { name, scope, description, mandatory2fa } = roleData;

	if (await Roles.findOneByName(name)) {
		throw new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');
	}

	if (!isValidRoleScope(scope)) {
		throw new MeteorError('error-invalid-scope', 'Invalid scope');
	}

	const role = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);

	void notifyOnRoleChanged(role);

	if (options.broadcastUpdate) {
		void api.broadcast('user.roleUpdate', {
			type: 'changed',
			_id: role._id,
		});
	}

	return role;

View on GitHub (pinned to b2c16d5842)