RocketChat/Rocket.Chat · error · Error
last-owner-can-not-be-removed
Error message
last-owner-can-not-be-removed
What it means
TeamService.removeMembers refuses to delete a team's last owner: before removing an owner it counts owners via TeamMember.countByTeamIdAndRole(team._id, 'owner') and throws when exactly one remains. The guard guarantees every team keeps at least one member with the 'owner' role able to manage members and settings. It fires per-member inside the loop, after the member-existence checks.
Source
Thrown at apps/meteor/server/services/team/service.ts:804
const byUser = await Users.findOneById(uid);
for await (const member of members) {
if (!member.userId) {
throw new Error('invalid-user');
}
const existingMember = await TeamMember.findOneByUserIdAndTeamId(member.userId, team._id);
const subscription = await Subscriptions.findOneByRoomIdAndUserId(team.roomId, member.userId);
if (!existingMember && !subscription) {
throw new Error('member-does-not-exist');
}
if (existingMember) {
if (existingMember.roles?.includes('owner')) {
const totalOwners = await TeamMember.countByTeamIdAndRole(team._id, 'owner');
if (totalOwners === 1) {
throw new Error('last-owner-can-not-be-removed');
}
}
await TeamMember.removeById(existingMember._id);
}
const removedUser = usersToRemove.find((u) => u._id === (existingMember || member).userId);
if (removedUser) {
await removeUserFromRoom(
team.roomId,
removedUser,
uid !== member.userId && byUser
? {
byUser,
}
: undefined,
);
}View on GitHub (pinned to e4b8178b20)
Solutions
- Promote another member to owner first via Team.addRolesToMember(teamId, successorId, ['owner']), then remove the old owner
- If the team should cease to exist, delete the team (teams.delete) instead of removing its last owner
- Before removal, count owners with TeamMember.countByTeamIdAndRole(teamId, 'owner') and block the action in the UI when it equals 1
- For teams.leave, transfer ownership before leaving
Example fix
// before
await Team.removeMembers(uid, teamId, [{ userId: lastOwnerId }]);
// after
const owners = await TeamMember.countByTeamIdAndRole(teamId, 'owner');
if (owners === 1) {
await Team.addRolesToMember(teamId, successorId, ['owner']);
}
await Team.removeMembers(uid, teamId, [{ userId: lastOwnerId }]); Defensive patterns
Strategy: validation
Validate before calling
const member = await TeamMember.findOneByUserIdAndTeamId(userId, teamId);
if (member?.roles?.includes('owner')) {
const totalOwners = await TeamMember.countByTeamIdAndRole(teamId, 'owner');
if (totalOwners === 1) {
throw new Error('promote-another-owner-first');
}
} Try / catch
try {
await Team.removeMembers(uid, teamId, members);
} catch (err) {
if (err instanceof Error && err.message === 'last-owner-can-not-be-removed') {
// surface 'transfer ownership first' to the user
}
throw err;
} Prevention
- Disable the Remove/Leave action for the last owner in the UI
- Run ownership transfer before owner removal in scripts
- Keep at least two owners on production teams
When it happens
Trigger: POST teams.removeMember (or teams.leave by the owner) targeting the sole owner of a team; a bulk removeMembers batch that includes the only owner; a single-member team whose creator tries to leave without transferring ownership.
Common situations: Solo teams where the creator is the only owner; cleanup scripts that remove all members in one batch; owner using teams.leave without first promoting someone else.
Related errors
- error-remove-last-owner
- Missing user to perform the upload operation
- invalid-calendar-event
- You are the last owner. Please set new owner before banning
- error-unit-cant-be-empty
AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18).
Data as JSON: /api/errors/3354c741d8328bb9.
Report an issue: GitHub.