RocketChat/Rocket.Chat · error · Error

This_conversation_is_already_closed

Error message

This_conversation_is_already_closed

What it means

Thrown by resumeRoomOnHold when `room.open` is false. You cannot resume a held chat that is already closed, because closing terminates the conversation. This guard precedes the onHold check.

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 f9d3ec372b)

Solutions

  1. Filter the held-chats list by open === true so closed rooms never offer a Resume action.
  2. Refresh the room before resume and tell the user the chat is closed if room.open is false.
  3. Check the close-on-hold timeout settings if held rooms are closing unexpectedly.

Example fix

// before
await omnichannelService.resumeRoomOnHold(room, comment, user);

// after
if (!room.open) {
  throw new Error('This conversation is already closed');
}
await omnichannelService.resumeRoomOnHold(room, comment, user);
Defensive patterns

Strategy: validation

Validate before calling

if (!room.open) throw new Error('This conversation is already closed');
await omnichannelService.resumeRoomOnHold(room, comment, user);

Type guard

function isOpenRoom(room: unknown): boolean {
  return !!room && (room as any).open === true;
}

Try / catch

try {
  await omnichannelService.resumeRoomOnHold(room, comment, user);
} catch (e) {
  if (e instanceof Error && e.message === 'This_conversation_is_already_closed') {
    notifyUser('This conversation is already closed.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling resumeRoomOnHold for a room whose `open` is false — a chat that was closed (agent closed it, visitor left, abandonment timeout) while it happened to also be on hold.

Common situations: A held chat expires and auto-closes, then an agent clicks Resume; the UI still lists the held chat after a close event; concurrent close + resume.

Related errors


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