RocketChat/Rocket.Chat · error · Meteor.Error
error-remove-last-owner
error-remove-last-owner
Error message
This is the last owner. Please set a new owner before removing this one.
What it means
Business rule in removeRoomOwner: if Roles.countUsersInRole('owner', rid) === 1, the sole remaining owner cannot be demoted — error-remove-last-owner. This keeps rooms (and their teams) from becoming ownerless and unmanageable.
Source
Thrown at apps/meteor/server/meteor-methods/rooms/removeRoomOwner.ts:63
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) {
void notifyOnSubscriptionChangedById(subscription._id);
}
const fromUser = await Users.findOneById(fromUserId);
if (!fromUser) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'removeRoomOwner',
});View on GitHub (pinned to b2c16d5842)
Solutions
- Promote another member to owner FIRST (addRoomOwner / POST /api/v1/channels.addOwner), then remove the previous owner
- If the room should not survive, delete the room instead of stripping its last owner
- For teams, make sure a team owner remains before touching the main channel's ownership
Example fix
// before
await Meteor.callAsync('removeRoomOwner', rid, currentOwnerId);
// after
await Meteor.callAsync('addRoomOwner', rid, newOwnerId);
await Meteor.callAsync('removeRoomOwner', rid, currentOwnerId); Defensive patterns
Strategy: validation
Validate before calling
// via REST: GET /api/v1/channels.roles?roomId=rid
// count entries whose roles include 'owner'; only allow removal when count > 1
const ownerCount = (await fetchRoles(rid)).filter((r) => r.roles.includes('owner')).length;
if (ownerCount < 2) {
// block: promote another owner first
} Try / catch
try {
await Meteor.callAsync('removeRoomOwner', rid, userId);
} catch (err) {
if (err instanceof Meteor.Error && err.error === 'error-remove-last-owner') {
// prompt user to select a successor, then addRoomOwner before retrying
}
} Prevention
- Always transfer ownership as promote-then-demote, never demote-then-promote
- Show a successor picker when only one owner remains
- Offboarding scripts should verify an owner will remain before stripping roles
When it happens
Trigger: Demoting the only owner of a channel; the previous co-owner already left so the target is the last one; ownership transfer scripted as remove-then-add instead of add-then-remove.
Common situations: Offboarding scripts stripping roles from a departing founder without promoting a successor first; teams where all other owners quit and were removed.
Related errors
- You are the last owner. Please set new owner before banning
- error-invalid-user
- error-not-allowed
- error-user-not-in-room
- error-user-already-leader
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/b9d822b2f7313d6b.
Report an issue: GitHub.