RocketChat/Rocket.Chat · error · MeteorError
error-invalid-roleId
error-invalid-roleId
Error message
This role does not exist
What it means
updateRole starts by resolving the target with Roles.findOneById(roleId); if nothing is found it throws MeteorError('error-invalid-roleId', 'This role does not exist'). The value must be the role document's _id, not the role name - passing a name (or a stale/deleted id) lands on this throw.
Solutions
- Re-fetch the roles list and use the current _id of the role you intend to change.
- If you only know the name, resolve it first: Roles.findOneByName(name) and use its _id.
- Refresh role admin UIs before saving edits so stale ids are not submitted.
Example fix
// before
await updateRole('moderator', { description: 'Can moderate' }); // name passed as id -> throws
// after
const role = await Roles.findOneByName('moderator');
if (!role) throw new MeteorError('error-invalid-roleId', 'This role does not exist');
await updateRole(role._id, { description: 'Can moderate' }); Defensive patterns
Strategy: validation
Validate before calling
const role = await Roles.findOneById(roleId);
if (!role) {
// stale or wrong id; re-fetch the roles list and use a current _id instead of calling updateRole
} Try / catch
try {
await updateRole(roleId, roleData);
} catch (e: any) {
if (e?.error === 'error-invalid-roleId') return notFound('role', roleId); // 404 to the client
throw e;
} Prevention
- Always resolve roles by name (Roles.findOneByName) when only the name is known, then use the returned _id.
- Refresh role admin views before saving edits so deleted roles are not targeted.
- Never hard-code role ids across environments; ids are per-database.
When it happens
Trigger: Calling updateRole with a roleId that was deleted in another session, a malformed id, or the role's name accidentally supplied in the id field.
Common situations: An admin edits a role in a tab while another admin deletes it, so the form posts a now-dead id; scripts hard-coding role ids that differ across environments (staging vs production); payload builders confusing name and _id.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- error-role-not-found
- error-action-not-allowed
- error-duplicate-role-names-not-allowed
- error-duplicate-role-names-not-allowed
- error-invalid-scope
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/df3fa74c74070445.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/lib/roles/updateRole.ts:20
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
import { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';
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');View on GitHub (pinned to b2c16d5842)