RocketChat/Rocket.Chat · warning · Meteor.Error
error-user-already-moderator
error-user-already-moderator
Error message
User is already a moderator
What it means
If the target's subscription already includes 'moderator' in its roles array, addRoomModerator throws error-user-already-moderator rather than rewriting the role. Like the leader variant this is an idempotency guard: the requested end state already holds, but the call still rejects, and it fires before beforeChangeRoomRole runs.
Source
Thrown at apps/meteor/server/meteor-methods/rooms/addRoomModerator.ts:64
const user = await Users.findOneById(userId);
if (!user?.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'addRoomModerator',
});
}
const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id);
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'addRoomModerator',
});
}
if (subscription.roles && Array.isArray(subscription.roles) === true && subscription.roles.includes('moderator') === true) {
throw new Meteor.Error('error-user-already-moderator', 'User is already a moderator', {
method: 'addRoomModerator',
});
}
await beforeChangeRoomRole.run({ fromUserId, userId, room, role: 'moderator' });
const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'moderator');
await syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.concat(['moderator']) || ['moderator']);
if (addRoleResponse.modifiedCount) {
void notifyOnSubscriptionChangedById(subscription._id);
}
const fromUser = await Users.findOneById(fromUserId);
if (!fromUser) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'addRoomLeader',
});View on GitHub (pinned to b2c16d5842)
Solutions
- Treat the error as success and refresh the member's roles in the UI.
- Check current roles before showing the promote action.
- Guard against double submission while a role change is in flight.
Example fix
// before
await Meteor.callAsync('addRoomModerator', rid, userId);
// after
try {
await Meteor.callAsync('addRoomModerator', rid, userId);
} catch (e: any) {
if (e?.error === 'error-user-already-moderator') return; // idempotent success
throw e;
} Defensive patterns
Strategy: validation
Validate before calling
// skip the call when the member already carries the role
const member = await fetch(`/api/v1/channels.members?roomId=${rid}`, { headers }).then((r) => r.json());
const alreadyModerator = member.members?.find((m: any) => m._id === userId)?.roles?.includes('moderator');
if (alreadyModerator) { /* nothing to do */ } Try / catch
try {
await Meteor.callAsync('addRoomModerator', rid, userId);
} catch (e: any) {
if (e?.error === 'error-user-already-moderator') return; // idempotent success
throw e;
} Prevention
- Read the subscription's current roles before offering the promote action.
- Guard against double-submits while a role change is pending.
- Map already-role errors to success in retry/idempotency wrappers.
When it happens
Trigger: Double-submitting the 'Set as moderator' action; two admins promoting the same user simultaneously; UI state stale relative to the subscription's roles.
Common situations: Buttons lacking pending/disabled handling; missed websocket updates making the role look absent; retry logic re-firing a completed request.
Related errors
- error-user-already-leader
- error-invalid-user
- error-not-allowed
- error-user-not-in-room
- error-invalid-room
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/63ac4a7e1f15a9c6.
Report an issue: GitHub.