RocketChat/Rocket.Chat · error · Error

error-room-already-closed

Error message

error-room-already-closed

What it means

placeRoomOnHold only operates on open conversations: after the room-type guard, if room.open is false it throws Error('error-room-already-closed'). Holding a chat exists to pause a live conversation for later resume, which is meaningless once the room is closed.

Solutions

  1. Re-fetch the room and check open before invoking; disable the on-hold action for closed rooms in the UI.
  2. Catch 'error-room-already-closed' and treat it as benign - the desired end state (chat not open) already holds.
  3. Sequence automation so hold attempts cannot be enqueued after close requests.

Example fix

// before
await service.placeRoomOnHold(staleRoom, comment, user); // staleRoom.open was true in the client only

// after
const fresh = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });
if (!fresh?.open) return; // already closed; nothing to hold
await service.placeRoomOnHold(fresh, comment, user);
Defensive patterns

Strategy: validation

Validate before calling

const fresh = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });
if (!fresh?.open) {
	// already closed; holding is not applicable - skip the call
}

Try / catch

try {
	await service.placeRoomOnHold(room, comment, user);
} catch (e: any) {
	if (e?.message === 'error-room-already-closed') return; // desired end state already holds; benign
	throw e;
}

Prevention

When it happens

Trigger: Calling placeRoomOnHold after the conversation was closed - the agent closed it, inactivity timeout closed it, or an API close raced the hold. Typical shape: stale client state letting the agent click 'on hold' on a chat that is already closed.

Common situations: Outdated room data in the client after a close event; two agents concurrently closing and holding the same chat; automation acting on an old room snapshot.

Related errors


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

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