RocketChat/Rocket.Chat · error · Error

error-cannot-place-chat-on-hold

Error message

error-cannot-place-chat-on-hold

What it means

With the setting 'Livechat_allow_manual_on_hold_upon_agent_engagement_only' enabled, on-hold is only allowed when the agent side engaged last: canAgentPlaceOnHold is computed as !room.lastMessage?.token (visitor messages carry a token), and Error('error-cannot-place-chat-on-hold') is thrown when the setting is on and the last message was the visitor's. With the setting disabled, this error cannot occur because canPlaceChatOnHold short-circuits to true.

Solutions

  1. Have the agent send a message before holding - that satisfies the 'agent engagement' precondition (last message without a token).
  2. If the workflow must hold right after visitor messages, disable 'Livechat_allow_manual_on_hold_upon_agent_engagement_only' in Livechat settings.
  3. Adjust automation to only attempt holds when room.lastMessage has no token (agent spoke last).

Example fix

// before
await service.placeRoomOnHold(room, comment, user); // last message was the visitor's; setting on -> throws

// after
const restricted = settings.get<boolean>('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
if (restricted && room.lastMessage?.token) {
	await sendMessage(agent, rid, 'Placing this chat on hold'); // engage first
}
await service.placeRoomOnHold(refetchedRoom, comment, user);
Defensive patterns

Strategy: validation

Validate before calling

const restricted = settings.get<boolean>('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
const agentEngagedLast = !room.lastMessage?.token; // visitor messages carry a token
if (restricted && !agentEngagedLast) {
	// agent must send a message first, or the setting must be off; do not call placeRoomOnHold yet
}

Try / catch

try {
	await service.placeRoomOnHold(room, comment, user);
} catch (e: any) {
	if (e?.message === 'error-cannot-place-chat-on-hold') { surface('Send a message to the visitor before placing the chat on hold'); return; }
	throw e;
}

Prevention

When it happens

Trigger: An agent clicks on-hold immediately after the visitor's message while the engagement-only setting is enabled; or queue automation tries to hold unanswered chats - with the setting on, any hold where the visitor sent the last message fails by design.

Common situations: The setting was enabled as policy after workflows were built assuming hold-anytime semantics; bots/auto-replies counted as agent engagement when they were not; agents unaware the last speaker must be the agent.

Related errors


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

Appendix: source

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

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

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

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

View on GitHub (pinned to b2c16d5842)