RocketChat/Rocket.Chat · error · Error
error-room-is-already-on-hold
Error message
error-room-is-already-on-hold
What it means
If room.onHold is already true, placeRoomOnHold throws Error('error-room-is-already-on-hold') rather than treating the repeat call as a no-op. The check runs third, after the room-type and open guards, so it fires on genuinely open livechat rooms that are already held.
Solutions
- Check room.onHold before calling and treat an already-held room as success.
- Prevent duplicate submissions: disable the button after click, add idempotency keys for retried requests.
- Re-fetch the room immediately before the call so the decision uses fresh state.
Example fix
// before
await service.placeRoomOnHold(room, comment, user); // second click -> error-room-is-already-on-hold
// after
if (room.onHold) return { alreadyOnHold: true }; // idempotent: already in the desired state
await service.placeRoomOnHold(room, comment, user); Defensive patterns
Strategy: validation
Validate before calling
if (room.onHold) {
// already held; treat as success and skip the call (idempotency at the caller)
} Try / catch
try {
await service.placeRoomOnHold(room, comment, user);
} catch (e: any) {
if (e?.message === 'error-room-is-already-on-hold') return { alreadyOnHold: true }; // idempotent success
throw e;
} Prevention
- Disable the hold button after first click to stop double submits.
- Re-read the onHold flag from the server before each hold attempt.
- Add idempotency keys when hold requests may be retried by the transport.
When it happens
Trigger: Double-invoking the on-hold action (double click, retried HTTP request without idempotency), or holding a room that another agent or automation already placed on hold.
Common situations: Duplicate submissions from flaky clients or retries; UI not reflecting the onHold flag set moments earlier by a different agent/session; bulk scripts processing a room list that overlaps with a concurrent hold job.
Related errors
- error-room-already-closed
- error-unserved-rooms-cannot-be-placed-onhold
- error-cannot-place-chat-on-hold
- error-invalid-room
- error-room-not-on-hold
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/eabada215cce0d95.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/local-services/omnichannel.internalService.ts:47
}
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),
Message.saveSystemMessage<IOmnichannelSystemMessage>('omnichannel_placed_chat_on_hold', roomId, '', onHoldBy, { comment }),
]);
View on GitHub (pinned to b2c16d5842)