RocketChat/Rocket.Chat · error · MeteorError

room-closed

room-closed

Error message

Room is closed

What it means

During join(), omnichannel rooms that are not open (room.open !== true, i.e. the conversation is closed) are rejected with Meteor error room-closed ('Room is closed', method 'joinRoom'). Closed omnichannel conversations cannot be re-joined as members; they must be reopened through the omnichannel flow.

Source

Thrown at apps/meteor/server/services/room/service.ts:190

		sendMessage = true,
	): Promise<void> {
		await saveRoomTopic(roomId, roomTopic, user, sendMessage);
	}

	async getRouteLink(room: AtLeast<IRoom, '_id' | 't' | 'name'>): Promise<string | boolean> {
		return roomCoordinator.getRouteLink(room.t as string, { rid: room._id, name: room.name });
	}

	/**
	 * Method called by users to join a room.
	 */
	async join({ room, user, joinCode }: { room: IRoom; user: IUser; joinCode?: string }) {
		if (!(await roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.JOIN, user._id))) {
			throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
		}

		if (isOmnichannelRoom(room) && !room.open) {
			throw new MeteorError('room-closed', 'Room is closed', { method: 'joinRoom' });
		}

		if (!(await Authorization.canAccessRoom(room, user))) {
			throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
		}

		if (
			FederationActions.shouldPerformFederationAction(room) &&
			!isUserNativeFederated(user) &&
			!(await FederationMatrix.canUserAccessFederation(user))
		) {
			throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation', { method: 'joinRoom' });
		}

		if (isRoomWithJoinCode(room) && !(await Authorization.hasPermission(user._id, 'join-without-join-code'))) {
			if (!joinCode) {
				throw new MeteorError('error-code-required', 'Code required', { method: 'joinRoom' });
			}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Re-open the conversation through the omnichannel UI/API (e.g. take/claim the inquiry) instead of calling join
  2. Filter out closed rooms before attempting a join
  3. On error, refresh the room state in the client and stop offering join for closed conversations
Defensive patterns

Strategy: validation

Validate before calling

import { isOmnichannelRoom } from '@rocket.chat/core-typings';

if (isOmnichannelRoom(room) && !room.open) {
  // closed conversation: reopen via omnichannel flow, never join()
  return reopenConversation(room._id);
}

Type guard

const isJoinableOmnichannelRoom = (room: IRoom): boolean =>
  !isOmnichannelRoom(room) || room.open === true;

Try / catch

try {
  await roomService.join({ room, user });
} catch (err) {
  if (err?.error === 'room-closed') {
    // refresh state and offer to reopen the conversation; join() will keep failing while closed
    return refreshRoomAndPromptReopen(room._id);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling join on an omnichannel room whose conversation is already closed — the visitor ended it, an agent closed it, or routing/timeout closed it.

Common situations: Agents trying to rejoin closed conversations directly via the join API; clients holding stale room references after the conversation closed.

Related errors


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