{"record":{"id":"3ba4ae44904d05c1","repo":"RocketChat/Rocket.Chat","slug":"error-role-protected","errorCode":"error-role-protected","errorMessage":"Role is protected","messagePattern":"Role is protected","errorType":"exception","errorClass":"MeteorError","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/roles/updateRole.ts","lineNumber":24,"sourceCode":"import { notifyOnRoleChangedById } from '../../../../server/lib/notifyListener';\n\ntype UpdateRoleOptions = {\n\tbroadcastUpdate?: boolean;\n};\n\nexport const updateRole = async (\n\troleId: IRole['_id'],\n\troleData: Omit<IRole, '_id' | '_updatedAt'>,\n\toptions: UpdateRoleOptions = {},\n): Promise<IRole> => {\n\tconst role = await Roles.findOneById(roleId);\n\n\tif (!role) {\n\t\tthrow new MeteorError('error-invalid-roleId', 'This role does not exist');\n\t}\n\n\tif (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {\n\t\tthrow new MeteorError('error-role-protected', 'Role is protected');\n\t}\n\n\tif (roleData.name) {\n\t\tconst otherRole = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });\n\t\tif (otherRole && otherRole._id !== role._id) {\n\t\t\tthrow new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t\t}\n\t} else {\n\t\troleData.name = role.name;\n\t}\n\n\tif (roleData.scope) {\n\t\tif (!isValidRoleScope(roleData.scope)) {\n\t\t\tthrow new MeteorError('error-invalid-scope', 'Invalid scope');\n\t\t}\n\t} else {\n\t\troleData.scope = role.scope;\n\t}","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/lib/roles/updateRole.ts#L6-L42","documentation":"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'.","triggerScenarios":"Submitting an update for a protected role where roleData.name !== role.name or roleData.scope !== role.scope. Description and mandatory2fa edits are allowed.","commonSituations":"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.","solutions":["Do not change name or scope on protected roles — edit only description/mandatory2fa.","If a name/scope change is truly required, unset protected on the model first (deliberate, rare).","Catch MeteorError 'error-role-protected' and inform the user the role is system-managed."],"exampleFix":"// before\nawait updateRole(roleId, { name: newName, scope: newScope });\n\n// after\nif (role.protected && (newName !== role.name || newScope !== role.scope)) {\n  throw new Error('protected role: name/scope immutable');\n}\nawait updateRole(roleId, { name: role.name, scope: role.scope, description, mandatory2fa });","handlingStrategy":"validation","validationCode":"if (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {\n  throw new Error('protected role: name/scope immutable');\n}","typeGuard":"const isProtectedRole = (role: IRole): role is IRole & { protected: true } => Boolean(role.protected);","tryCatchPattern":"try { await updateRole(roleId, roleData); }\ncatch (e) {\n  if (e?.code === 'error-role-protected') { /* inform user */ return; }\n  throw e;\n}","preventionTips":["Hide/disable name and scope fields for protected roles in the UI.","Never rename built-in roles in scripts."],"tags":["roles","protected","meteor-error"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}