RocketChat/Rocket.Chat · error · MeteorError

error-role-protected

error-role-protected

Error message

Role is protected

What it means

Thrown by updateRole in updateRole.ts:24 when the role is marked protected AND the caller tries to change its name or scope. Protected roles (e.g. admin, user, bot, guest) cannot be renamed or rescoped. MeteorError code 'error-role-protected'.

Source

Thrown at apps/meteor/ee/server/lib/roles/updateRole.ts:24

import { notifyOnRoleChangedById } from '../../../../server/lib/notifyListener';

type UpdateRoleOptions = {
	broadcastUpdate?: boolean;
};

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;
	}

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Do not change name or scope on protected roles — edit only description/mandatory2fa.
  2. If a name/scope change is truly required, unset protected on the model first (deliberate, rare).
  3. Catch MeteorError 'error-role-protected' and inform the user the role is system-managed.

Example fix

// before
await updateRole(roleId, { name: newName, scope: newScope });

// after
if (role.protected && (newName !== role.name || newScope !== role.scope)) {
  throw new Error('protected role: name/scope immutable');
}
await updateRole(roleId, { name: role.name, scope: role.scope, description, mandatory2fa });
Defensive patterns

Strategy: validation

Validate before calling

if (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {
  throw new Error('protected role: name/scope immutable');
}

Type guard

const isProtectedRole = (role: IRole): role is IRole & { protected: true } => Boolean(role.protected);

Try / catch

try { await updateRole(roleId, roleData); }
catch (e) {
  if (e?.code === 'error-role-protected') { /* inform user */ return; }
  throw e;
}

Prevention

When it happens

Trigger: Submitting an update for a protected role where roleData.name !== role.name or roleData.scope !== role.scope. Description and mandatory2fa edits are allowed.

Common situations: Admin UI pre-fills a protected role's form and the user changes the name field; migration script tries to normalize built-in role names.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/3ba4ae44904d05c1. Report an issue: GitHub.