RocketChat/Rocket.Chat · warning · Meteor.Error

error-user-already-leader

error-user-already-leader

Error message

User is already a leader

What it means

If the target's subscription already lists 'leader' in its roles array, addRoomLeader throws error-user-already-leader instead of writing the role again. This is an idempotency guard: the desired end state is already in place, so the write is skipped. Semantically benign, but it still fails the method call.

Source

Thrown at apps/meteor/server/meteor-methods/rooms/addRoomLeader.ts:48

	const user = await Users.findOneById(userId);

	if (!user?.username) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user', {
			method: 'addRoomLeader',
		});
	}

	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: 'addRoomLeader',
		});
	}

	if (subscription.roles && Array.isArray(subscription.roles) === true && subscription.roles.includes('leader') === true) {
		throw new Meteor.Error('error-user-already-leader', 'User is already a leader', {
			method: 'addRoomLeader',
		});
	}

	const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'leader');
	await syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.concat(['leader']) || ['leader']);

	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 - the user already has the role; refresh the UI state from the subscription.
  2. Check the member's current roles before offering the action.
  3. Disable the action while a role change is in flight.

Example fix

// before
await Meteor.callAsync('addRoomLeader', rid, userId);

// after
try {
	await Meteor.callAsync('addRoomLeader', rid, userId);
} catch (e: any) {
	if (e?.error === 'error-user-already-leader') return; // already done
	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 alreadyLeader = member.members?.find((m: any) => m._id === userId)?.roles?.includes('leader');
if (alreadyLeader) { /* nothing to do */ }

Try / catch

try {
	await Meteor.callAsync('addRoomLeader', rid, userId);
} catch (e: any) {
	if (e?.error === 'error-user-already-leader') return; // idempotent success
	throw e;
}

Prevention

When it happens

Trigger: Double-submit of the 'Set as leader' action (double click, retry); two admins promoting the same user concurrently; stale UI that does not reflect the current role.

Common situations: Buttons without pending/disabled states; optimistic UI that assumes failure and re-triggers; websocket updates missed so the role appears unset.

Related errors


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