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

  1. Promote another member to owner first via Team.addRolesToMember(teamId, successorId, ['owner']), then remove the old owner
  2. If the team should cease to exist, delete the team (teams.delete) instead of removing its last owner
  3. Before removal, count owners with TeamMember.countByTeamIdAndRole(teamId, 'owner') and block the action in the UI when it equals 1
  4. 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

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


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