RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-room

error-invalid-room

Error message

Invalid room

What it means

unblockUserMethod loads both subscriptions in the room — the blocked user's and the blocker's — via Subscriptions.findOneByRoomIdAndUserId; if either is missing it throws error-invalid-room. Despite the code name, the usual meaning is not 'bad rid' but 'one of the two users is not (or no longer) a member of this room'.

Source

Thrown at apps/meteor/server/lib/users/unblockUser.ts:13

import { Subscriptions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../notifyListener';

export const unblockUserMethod = async (userId: string, { rid, blocked }: { rid: string; blocked: string }): Promise<void> => {
	const [blockedUser, blockerUser] = await Promise.all([
		Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }),
		Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }),
	]);

	if (!blockedUser || !blockerUser) {
		throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'unblockUser' });
	}

	const [blockedResponse, blockerResponse] = await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId);

	const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])];

	if (listenerUsers.length) {
		void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers);
	}
};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Verify both users still have subscriptions in the room before offering the unblock action
  2. Refresh room/membership state from the server when the action fails rather than retrying blindly
  3. Pass rid from the currently open room context, never from persisted state

Example fix

// before
await unblockUserMethod(userId, { rid, blocked }); // error-invalid-room after the target left

// after
const [blockedSub, mySub] = await Promise.all([
  Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }),
  Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }),
]);
if (!blockedSub || !mySub) throw new Meteor.Error('error-invalid-room', 'Invalid room');
await unblockUserMethod(userId, { rid, blocked });
Defensive patterns

Strategy: validation

Validate before calling

import { Subscriptions } from '@rocket.chat/models';

const [target, self] = await Promise.all([
  Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }),
  Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }),
]);
if (!target || !self) {
  // hide the unblock action; membership changed
}

Try / catch

try {
  await unblockUserMethod(userId, { rid, blocked });
} catch (error) {
  if (error instanceof Meteor.Error && error.error === 'error-invalid-room') {
    // refetch subscriptions and update the member list UI
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: Calling the unblockUser method after the blocked user left the room; using a rid from stale client state (the UI switched rooms); invoking unblock in a context where the caller has no subscription to rid.

Common situations: Direct-message member lists kept in client state after the other party leaves; races between leave-room and moderation actions; wiring the rid parameter from the wrong room.

Related errors


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