RocketChat/Rocket.Chat · error · Meteor.Error

error-user-not-owner

error-user-not-owner

Error message

User is not an owner

What it means

removeRoomOwner requires the target's subscription.roles to be an array that includes 'owner'; otherwise it throws error-user-not-owner. Unlike the moderator check, this one also fires when roles is undefined or missing entirely.

Source

Thrown at apps/meteor/server/meteor-methods/rooms/removeRoomOwner.ts:55

	}

	const user = await Users.findOneById(userId);
	if (!user?.username) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user', {
			method: 'removeRoomOwner',
		});
	}

	const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id);

	if (!subscription) {
		throw new Meteor.Error('error-invalid-room', 'Invalid room', {
			method: 'removeRoomOwner',
		});
	}

	if (Array.isArray(subscription.roles) === false || subscription.roles?.includes('owner') === false) {
		throw new Meteor.Error('error-user-not-owner', 'User is not an owner', {
			method: 'removeRoomOwner',
		});
	}

	const numOwners = await Roles.countUsersInRole('owner', rid);

	if (numOwners === 1) {
		throw new Meteor.Error('error-remove-last-owner', 'This is the last owner. Please set a new owner before removing this one.', {
			method: 'removeRoomOwner',
		});
	}

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

	const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'owner');
	await syncRoomRolePriorityForUserAndRoom(userId, rid, subscription.roles?.filter((r) => r !== 'owner') || []);

	if (removeRoleResponse.modifiedCount) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Inspect the target's subscription roles and only offer 'Remove owner' when they include 'owner'
  2. Refresh the member/roles list right before rendering actions
  3. Catch error-user-not-owner and treat it as 'already demoted'

Example fix

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

// after
const sub = Subscriptions.findOne({ rid, 'u._id': userId });
const roles = sub?.roles;
if (!Array.isArray(roles) || !roles.includes('owner')) return;
await Meteor.callAsync('removeRoomOwner', rid, userId);
Defensive patterns

Strategy: type-guard

Validate before calling

const sub = Subscriptions.findOne({ rid, 'u._id': userId });
if (!Array.isArray(sub?.roles) || !sub.roles.includes('owner')) {
  // not an owner: skip removal
}

Type guard

const hasRoomRole = (roles: string[] | undefined | null, role: string): boolean =>
  Array.isArray(roles) && roles.includes(role);

const isOwner = (sub?: { roles?: string[] }): boolean => hasRoomRole(sub?.roles, 'owner');

Try / catch

try {
  await Meteor.callAsync('removeRoomOwner', rid, userId);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'error-user-not-owner') {
    // already demoted: refresh and continue
  }
}

Prevention

When it happens

Trigger: Calling removeRoomOwner on a plain member or a moderator; the owner role was already removed by another admin; subscription.roles missing entirely.

Common situations: Stale 'Owner' badge in a members modal after the role was stripped elsewhere; racing double-invocations of the action.

Related errors


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