RocketChat/Rocket.Chat · error · Error
error-room-already-closed
Error message
error-room-already-closed
What it means
Thrown by OmnichannelInternalService.placeRoomOnHold when the target room's `open` flag is falsy. The service refuses to put a closed conversation on hold because on-hold is a state that only applies to active (open) chats. The guard runs after the room-type check and before any hold-specific logic.
Source
Thrown at apps/meteor/ee/server/local-services/omnichannel.internalService.ts:44
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');
}
const [roomResult, subsResult] = await Promise.all([
LivechatRooms.setOnHoldByRoomId(roomId),
Subscriptions.setOnHoldByRoomId(roomId),View on GitHub (pinned to f9d3ec372b)
Solutions
- Refresh the room from LivechatRooms before offering the on-hold action and hide/disable the control when room.open === false.
- If you call placeRoomOnHold directly, pre-check `room.open` and surface 'room is closed' to the user instead of letting the server throw.
- Investigate why the room reached your code in a closed state (close-on-abandon, transfer, or another concurrent close call).
Example fix
// before
await omnichannelService.placeRoomOnHold(room, comment, user);
// after
if (!room.open) {
throw new Error('Room is closed; cannot place on hold');
}
await omnichannelService.placeRoomOnHold(room, comment, user); Defensive patterns
Strategy: validation
Validate before calling
const room = await LivechatRooms.findOneById(roomId, { projection: { open: 1, t: 1 } });
if (!room || !isOmnichannelRoom(room)) throw new Error('Invalid omnichannel room');
if (!room.open) throw new Error('Room is closed; cannot place on hold');
// safe to call placeRoomOnHold Type guard
function canPlaceOnHold(room: unknown): room is IOmnichannelRoom {
return !!room && typeof room === 'object'
&& (room as any).t === 'l'
&& (room as any).open === true;
} Try / catch
try {
await omnichannelService.placeRoomOnHold(room, comment, user);
} catch (e) {
if (e instanceof Error && e.message === 'error-room-already-closed') {
notifyUser('This conversation is already closed.');
return;
}
throw e;
} Prevention
- Drive the Hold button visibility off room.open === true from the live room document.
- Disable action controls while a room request is in flight to avoid stale-state calls.
- Subscribe to room-change notifications so the UI reflects closes immediately.
When it happens
Trigger: Calling the on-hold flow (livechat onHold method / REST route that delegates to placeRoomOnHold) for a room whose `open` field is false in the LivechatRooms collection, e.g. a chat that was already closed via /livechat/room.close or that auto-closed.
Common situations: Stale client UI that still shows a 'Place on hold' button after the room was closed by another agent or by a visitor inactivity timeout; race where two agents act on the same room; cached room document passed into the service after a close.
Related errors
- This_conversation_is_already_closed
- This_conversation_is_already_closed
- error-invalid-room
- error-invalid-sla
- error-forwarding-department-target-not-allowed
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/d86cd311d99c5cf5.
Report an issue: GitHub.