RocketChat/Rocket.Chat · error · Error

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

Error message

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

What it means

Thrown by placeRoomOnHold when `room.servedBy` is falsy. A chat without a serving agent cannot be put on hold because there is no agent relationship to suspend. This is the last guard before the room/subscription update.

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 f9d3ec372b)

Solutions

  1. Only expose Hold for rooms with an active servedBy; gate the UI on room.servedBy being truthy.
  2. Re-fetch the room to confirm it is still assigned before calling placeRoomOnHold.
  3. If the chat is queued, route it through the assignment flow first, not the hold flow.

Example fix

// before
await omnichannelService.placeRoomOnHold(room, comment, user);

// after
if (!room.servedBy) {
  throw new Error('Room has no serving agent; cannot place on hold');
}
await omnichannelService.placeRoomOnHold(room, comment, user);
Defensive patterns

Strategy: validation

Validate before calling

if (!room.servedBy) {
  throw new Error('Room has no serving agent; cannot place on hold');
}
await omnichannelService.placeRoomOnHold(room, comment, user);

Type guard

function hasServingAgent(room: unknown): boolean {
  return !!room && !!(room as any).servedBy;
}

Try / catch

try {
  await omnichannelService.placeRoomOnHold(room, comment, user);
} catch (e) {
  if (e instanceof Error && e.message === 'error-unserved-rooms-cannot-be-placed-onhold') {
    notifyUser('This chat has no assigned agent.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling placeRoomOnHold on a room whose servedBy field is null/undefined: an unclaimed queued inquiry, a room whose agent was removed, or a chat returned to the queue.

Common situations: Trying to hold a chat that is still queued (no agent assigned); the servedBy relation was cleared by a transfer/abandon flow before the hold call.

Related errors


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