RocketChat/Rocket.Chat · warning · Error

member-does-not-exist

Error message

member-does-not-exist

What it means

Thrown by TeamService.removeMembers when, for a given member entry, neither a TeamMember document nor a subscription on the team's main room exists (TeamMember.findOneByUserIdAndTeamId and Subscriptions.findOneByRoomIdAndUserId both come back empty). The service checks both collections because a participant may exist only as a room subscriber; finding neither means the user is not on the team. The throw aborts the batch loop, so entries after the failing member are not processed.

Solutions

  1. Check membership first with TeamMember.findOneByUserIdAndTeamId(userId, teamId) (and the main-room subscription) and treat absence as success
  2. Re-fetch the member list (GET teams.members) immediately before removing to avoid stale ids
  3. Make the removal flow idempotent: catch member-does-not-exist and return success since the goal state already holds
  4. Verify the userId exists in this workspace via Users.findOneById

Example fix

// before
await Team.removeMembers(uid, teamId, [{ userId }]);

// after
const existing = await TeamMember.findOneByUserIdAndTeamId(userId, teamId);
if (!existing) {
  // user already not on team — nothing to do
  return;
}
await Team.removeMembers(uid, teamId, [{ userId }]);
Defensive patterns

Strategy: validation

Validate before calling

const member = await TeamMember.findOneByUserIdAndTeamId(userId, teamId, { projection: { _id: 1 } });
const sub = member ? null : await Subscriptions.findOneByRoomIdAndUserId(team.roomId, userId);
if (!member && !sub) {
  // user is not on the team: nothing to remove
  return;
}

Try / catch

try {
  await Team.removeMembers(uid, teamId, [{ userId }]);
} catch (err) {
  if (err instanceof Error && err.message === 'member-does-not-exist') return; // idempotent no-op
  throw err;
}

Prevention

When it happens

Trigger: Calling Team.removeMembers / POST teams.removeMember with a userId that was already removed (double-submit, two admins removing at once), a user who left via teams.leave, or an id that never belonged to this workspace.

Common situations: Stale member list in the admin UI after someone else already removed the user; retry of a timed-out request that actually succeeded; userId copied from a different environment/database.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at apps/meteor/server/services/team/service.ts:797

		});
		if (!team) {
			throw new Error('team-does-not-exist');
		}

		const membersIds = members.map((m) => m.userId);
		const usersToRemove = await Users.findByIds(membersIds).toArray();
		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,

View on GitHub (pinned to e4b8178b20)