RocketChat/Rocket.Chat · warning · Meteor.Error

error-user-already-owner

error-user-already-owner

Error message

User is already an owner

What it means

Thrown by addRoomOwner() in apps/meteor/server/meteor-methods/rooms/addRoomOwner.ts:64 when the target's subscription already has 'owner' in its roles array. The check is explicit (Array.isArray + includes) and acts as an idempotency guard so the role is not re-added, no system message is sent, and no federation event fires for a no-op.

Source

Thrown at apps/meteor/server/meteor-methods/rooms/addRoomOwner.ts:64

	const user = await Users.findOneById(userId);

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

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

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

	await beforeChangeRoomRole.run({ fromUserId, userId, room, role: 'owner' });

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

	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 this error as benign if it comes from a retry: verify current roles and skip instead of re-calling.
  2. Pre-check before calling: fetch the subscription and return early when subscription.roles?.includes('owner').
  3. Make client actions idempotent (disable the button while in-flight) so the second call never happens.
  4. For sync jobs, reconcile by diffing current roles against desired roles rather than blindly applying.

Example fix

// before
await addRoomOwner(uid, rid, targetUserId); // may throw error-user-already-owner

// after
const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, targetUserId, { projection: { roles: 1 } });
if (sub?.roles?.includes('owner')) return; // already owner, no-op
await addRoomOwner(uid, rid, targetUserId);
Defensive patterns

Strategy: type-guard

Validate before calling

const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { roles: 1 } });
if (sub?.roles?.includes('owner')) return; // already owner: skip instead of throwing

Type guard

const isAlreadyOwner = (sub: { roles?: string[] } | null): boolean =>
  Array.isArray(sub?.roles) === true && sub!.roles!.includes('owner');

Try / catch

try {
  await addRoomOwner(uid, rid, userId);
} catch (e) {
  if (e instanceof Meteor.Error && e.error === 'error-user-already-owner') {
    // benign no-op: treat as success in idempotent flows
    return true;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling addRoomOwner twice for the same user; retrying after a timeout when the first call actually succeeded; UI double-submission; scripts syncing roles that do not read the current subscription.roles before writing; a previous partial run added the role and then the method was re-invoked.

Common situations: Retry logic without idempotency checks after network flaps; bulk role-sync jobs replaying their whole list; two admins promoting the same person simultaneously; client re-render triggering a duplicate method call.

Related errors


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