RocketChat/Rocket.Chat · error · Error

error-unserved-rooms-cannot-be-placed-onhold

Error message

error-unserved-rooms-cannot-be-placed-onhold

What it means

placeRoomOnHold requires an assigned agent: if room.servedBy is unset it throws Error('error-unserved-rooms-cannot-be-placed-onhold'). On-hold semantics depend on the serving agent (resume returns the chat to them), so chats still in queue or whose servedBy was cleared cannot be held.

Solutions

  1. Assign an agent (take the chat) before placing it on hold.
  2. Exclude rooms without servedBy from bulk on-hold operations.
  3. If servedBy was unexpectedly cleared, fix department/agent routing before retrying the hold.

Example fix

// before
for (const r of openRooms) await service.placeRoomOnHold(r, comment, user); // queued rooms throw

// after
for (const r of openRooms) {
	if (!r.servedBy) continue; // unserved/queued chats cannot be held
	await service.placeRoomOnHold(r, comment, user);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!room.servedBy) {
	// no assigned agent (still queued or assignment cleared); on-hold is not applicable
}

Try / catch

try {
	await service.placeRoomOnHold(room, comment, user);
} catch (e: any) {
	if (e?.message === 'error-unserved-rooms-cannot-be-placed-onhold') { surface('Assign an agent before placing the chat on hold'); return; }
	throw e;
}

Prevention

When it happens

Trigger: Attempting to hold a chat that was never taken by an agent (still queued) or whose servedBy was cleared - e.g. the agent was removed, the chat was returned to the queue, or department re-routing reset assignment.

Common situations: Bulk on-hold automation sweeping all open rooms including untouched queue items; stale room data missing servedBy; chats returned to queue between the UI read and the hold call.

Related errors


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

Appendix: source

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

		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),
			Message.saveSystemMessage<IOmnichannelSystemMessage>('omnichannel_placed_chat_on_hold', roomId, '', onHoldBy, { comment }),
		]);

		if (roomResult.modifiedCount) {
			void notifyOnRoomChangedById(roomId);
		}

		if (subsResult.modifiedCount) {
			void notifyOnSubscriptionChangedByRoomId(roomId);
		}

		await callbacks.run('livechat:afterOnHold', room);
	}

View on GitHub (pinned to b2c16d5842)