RocketChat/Rocket.Chat · error · Error

error-invalid-room

Error message

error-invalid-room

What it means

OmnichannelInternalService.placeRoomOnHold requires the passed room to be a livechat room: it throws Error('error-invalid-room') when the room is falsy or fails isOmnichannelRoom (i.e. room.t !== 'l'). This is the entry guard that runs before all on-hold state checks (open, onHold, servedBy).

Solutions

  1. Filter actions to omnichannel rooms (t === 'l') before offering or invoking on-hold.
  2. Handle room lookup misses before calling the service so null never reaches it.
  3. Re-fetch the room with its t field included when building the call.

Example fix

// before
await service.placeRoomOnHold(anyRoom, comment, user); // anyRoom.t may be 'c'/'d' -> throws

// after
if (!room || !isOmnichannelRoom(room)) throw new Error('error-invalid-room');
await service.placeRoomOnHold(room, comment, user);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!room || !isOmnichannelRoom(room)) {
	// not a livechat room (t !== 'l'); do not offer or invoke placeRoomOnHold
}

Type guard

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

const isHoldableRoom = (room: unknown): room is Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold'> =>
	typeof room === 'object' && room !== null && isOmnichannelRoom(room);

Try / catch

try {
	await service.placeRoomOnHold(room, comment, user);
} catch (e: any) {
	if (e?.message === 'error-invalid-room') return badRequest('Room is not an omnichannel room');
	throw e;
}

Prevention

When it happens

Trigger: Calling placeRoomOnHold with a non-omnichannel room object - a channel, direct message, or team room (t is 'c'/'d'/'p'/'t') - or with null/undefined, e.g. wiring a generic rooms-list action to the on-hold flow without filtering by type, or not handling a lookup miss.

Common situations: Generic room action handlers that show 'place on hold' for every room type; null rooms passed after an unhandled findOneById miss; test fixtures using plain room objects without t: 'l'.

Related errors


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

Appendix: 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 b2c16d5842)