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
- Make provisioning idempotent: look the role up by name first and reuse the existing one instead of erroring.
- Choose a name that does not collide with built-in or existing roles.
- 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
- Make role-provisioning scripts idempotent (find-or-create).
- Avoid names matching built-in roles (admin, moderator, guest, bot, livechat-agent).
- Reflect uniqueness in the role form: check the name against the roles list before submit.
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
- 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/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)