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

  1. Re-fetch the room by _id (LivechatRooms.findOneById) immediately before calling resumeRoomOnHold and abort if open is false
  2. In the UI, disable or hide the 'resume' action when room.open is false and show a 'conversation closed' notice
  3. If close and resume race, catch this error, re-read the room, and report 'already closed' instead of retrying
  4. 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

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


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)