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
- Fetch the room with the t field included and confirm isOmnichannelRoom(room) before calling.
- Type-narrow the caller to omnichannel rooms (AtLeast<IOmnichannelRoom,...>) at the boundary.
- 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
- Always project the t field when fetching rooms for hold operations.
- Narrow with isOmnichannelRoom at the service boundary.
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
- room-still-open
- improper-room-state
- error-room-already-closed
- This_conversation_is_already_closed
- This_conversation_is_already_closed
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/7f020e23c7adfd0b.
Report an issue: GitHub.