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
- Have the agent send a message before holding - that satisfies the 'agent engagement' precondition (last message without a token).
- If the workflow must hold right after visitor messages, disable 'Livechat_allow_manual_on_hold_upon_agent_engagement_only' in Livechat settings.
- 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
- Know the 'Livechat_allow_manual_on_hold_upon_agent_engagement_only' setting before building hold automation.
- Only attempt holds when the last message lacks a token (agent side spoke last).
- Update agent training/scripts: with the setting on, hold is an after-reply action.
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
- error-invalid-room
- error-room-already-closed
- error-room-is-already-on-hold
- error-unserved-rooms-cannot-be-placed-onhold
- AutoCloseOnHoldScheduler is not running
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)