RocketChat/Rocket.Chat · error · Error
This_conversation_is_already_closed
Error message
This_conversation_is_already_closed
What it means
Thrown by OmnichannelInternalService.resumeRoomOnHold (apps/meteor/ee/server/local-services/omnichannel.internalService.ts:90) when an agent tries to resume an on-hold Omnichannel conversation whose room document has open === false. The method validates room state before mutating anything, so this means the conversation was already closed (by the visitor, another agent, auto-close, or REST close) and cannot be resumed.
Solutions
- Re-fetch the room by _id (LivechatRooms.findOneById) immediately before calling resumeRoomOnHold and abort if open is false
- In the UI, disable or hide the 'resume' action when room.open is false and show a 'conversation closed' notice
- If close and resume race, catch this error, re-read the room, and report 'already closed' instead of retrying
- Check whether on-hold auto-close settings (Livechat chat closing timer for on-hold conversations) closed the room and adjust the workflow
Example fix
// before
const room = await LivechatRooms.findOneById(rid);
await omnichannelService.resumeRoomOnHold(room, comment, agent);
// after
const room = await LivechatRooms.findOneById(rid);
if (!room?.open) {
throw new Error('This_conversation_is_already_closed');
}
await omnichannelService.resumeRoomOnHold(room, comment, agent); Defensive patterns
Strategy: validation
Validate before calling
const room = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });
if (!room || !isOmnichannelRoom(room) || !room.open) {
// conversation closed — do not attempt resume
throw new Error('This_conversation_is_already_closed');
}
await omnichannelService.resumeRoomOnHold(room, comment, agent); Type guard
const isResumableOnHoldRoom = (
room: IOmnichannelRoom | null | undefined,
): room is IOmnichannelRoom & { open: true; onHold: true; servedBy: NonNullable<IOmnichannelRoom['servedBy']> } =>
!!room && isOmnichannelRoom(room) && room.open === true && room.onHold === true && !!room.servedBy; Try / catch
try {
await omnichannelService.resumeRoomOnHold(room, comment, agent);
} catch (e) {
if (e instanceof Error && e.message === 'This_conversation_is_already_closed') {
// re-sync room state and inform the user; do not retry
}
throw e;
} Prevention
- Always re-fetch the room immediately before resume-on-hold instead of trusting client state
- Disable the resume action in the UI as soon as room.open turns false
- Review on-hold auto-close timers so agents are not resuming chats a timer already closed
When it happens
Trigger: Calling resumeRoomOnHold(room, comment, resumeBy) — from the client 'resume on hold' action or server code — with a room object whose open flag is false. Typically the caller holds a stale room fetched before the room was closed elsewhere, or a close and resume race each other.
Common situations: Agent UI keeps stale room state after the conversation was closed in another tab/agent client or via REST; Livechat auto-close closed the on-hold chat; user re-triggers an old 'resume' action after the chat ended.
Related errors
- error-invalid-inquiry
- error-invalid-room
- error-room-already-closed
- error-room-is-already-on-hold
- error-room-not-on-hold
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/ff0c04bcf762e82a.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/local-services/omnichannel.internalService.ts:90
}
await callbacks.run('livechat:afterOnHold', room);
}
async resumeRoomOnHold(
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy'>,
comment: string,
resumeBy: Pick<IUser, '_id' | 'username' | 'name'>,
clientAction = false,
) {
this.logger.debug({ msg: 'Attempting to resume room on hold', roomId: room._id, userId: resumeBy?._id });
if (!room || !isOmnichannelRoom(room)) {
throw new Error('error-invalid-room');
}
if (!room.open) {
throw new Error('This_conversation_is_already_closed');
}
if (!room.onHold) {
throw new Error('error-room-not-on-hold');
}
const { _id: roomId, servedBy } = room;
if (!servedBy) {
this.logger.error({ msg: 'No serving agent found for room', roomId });
throw new Error('error-room-not-served');
}
const inquiry = await LivechatInquiry.findOneByRoomId(roomId, {});
if (!inquiry) {
this.logger.error({ msg: 'No inquiry found for room', roomId });
throw new Error('error-invalid-inquiry');
}View on GitHub (pinned to b2c16d5842)