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

  1. Treat the error as success and refresh the member's roles in the UI.
  2. Check current roles before showing the promote action.
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/63ac4a7e1f15a9c6. Report an issue: GitHub.