RocketChat/Rocket.Chat · error · Error

error-invalid-room

Error message

error-invalid-room

What it means

Thrown by placeRoomOnHold in omnichannel.internalService.ts:41 when the room is falsy or fails isOmnichannelRoom (room.t !== 'l'). This guards the hold operation so non-omnichannel or untyped rooms never enter the hold pipeline. NOTE: plain `new Error('error-invalid-room')`; followed immediately by related guards for closed and already-on-hold states.

Source

Thrown at apps/meteor/ee/server/local-services/omnichannel.internalService.ts:41

	logger: Logger;

	constructor() {
		super();
		this.logger = new Logger('OmnichannelEE');
	}

	async placeRoomOnHold(
		room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold'>,
		comment: string,
		onHoldBy: Pick<IUser, '_id' | 'username' | 'name'>,
	) {
		this.logger.debug({ msg: 'Attempting to place room on hold', roomId: room._id, userId: onHoldBy?._id });

		const { _id: roomId } = room;

		if (!room || !isOmnichannelRoom(room)) {
			throw new Error('error-invalid-room');
		}
		if (!room.open) {
			throw new Error('error-room-already-closed');
		}
		if (room.onHold) {
			throw new Error('error-room-is-already-on-hold');
		}
		const restrictedOnHold = settings.get('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
		const canRoomBePlacedOnHold = !room.onHold;
		const canAgentPlaceOnHold = !room.lastMessage?.token;
		const canPlaceChatOnHold = canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold);
		if (!canPlaceChatOnHold) {
			throw new Error('error-cannot-place-chat-on-hold');
		}
		if (!room.servedBy) {
			throw new Error('error-unserved-rooms-cannot-be-placed-onhold');
		}

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Fetch the room with the t field included and confirm isOmnichannelRoom(room) before calling.
  2. Type-narrow the caller to omnichannel rooms (AtLeast<IOmnichannelRoom,...>) at the boundary.
  3. Match by message 'error-invalid-room' and reject the request as 400.

Example fix

// before
const room = await Rooms.findOneById(rid, { projection: { open: 1, onHold: 1 } });
await svc.placeRoomOnHold(room, comment, user);

// after
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
const room = await Rooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1 } });
if (!room || !isOmnichannelRoom(room)) throw new Error('not an omnichannel room');
await svc.placeRoomOnHold(room, comment, user);
Defensive patterns

Strategy: type-guard

Validate before calling

import { isOmnichannelRoom } from '@rocket.chat/core-typings';
const room = await Rooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1 } });
if (!room || !isOmnichannelRoom(room)) throw new Error('not an omnichannel room');

Type guard

import { isOmnichannelRoom, type IOmnichannelRoom, type IRoom } from '@rocket.chat/core-typings';
// isOmnichannelRoom = (room: Pick<IRoom,'t'>): room is IOmnichannelRoom & IRoom => room.t === 'l';

Try / catch

try { await svc.placeRoomOnHold(room, comment, user); }
catch (e) {
  if (e instanceof Error && e.message === 'error-invalid-room') return res.status(400).send({ error: 'invalid-room' });
  throw e;
}

Prevention

When it happens

Trigger: Calling placeRoomOnHold with a room whose type is not 'l' (e.g. a channel, direct message, or a hand-built object with the wrong/missing t field), or with null/undefined after a bad fetch.

Common situations: Service routing bug passes a non-omnichannel room; room fetched with a projection that excluded t; cross-feature integration assumes all rooms are placeable on hold.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/7f020e23c7adfd0b. Report an issue: GitHub.