RocketChat/Rocket.Chat · warning · Error

error-room-already-closed

error-room-already-closed

Error message

error-room-already-closed

What it means

The outer closeLivechatRoom wrapper throws Error('error-room-already-closed') when the loaded room has open=false and forceClose was not requested. Its purpose is to stop double-closing an omnichannel room; passing forceClose: true bypasses it and re-runs the close routine.

Source

Thrown at apps/meteor/server/lib/omnichannel/closeLivechatRoom.ts:78

						emailTranscript: {
							sendToVisitor: false,
						},
					}),
		}),
	};

	if (forceClose) {
		return closeRoom({
			room,
			user,
			options,
			comment,
			forceClose,
		});
	}

	if (!room.open) {
		throw new Error('error-room-already-closed');
	}

	return closeRoom({
		room,
		user,
		options,
		comment,
	});
};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Treat the error as success in idempotent close flows - the desired end state is already reached
  2. Guard the close control against double submission and refresh room state after closing succeeds
  3. Pass forceClose: true only when re-closing an already-closed room is genuinely intended (e.g. correcting closing metadata)

Example fix

// before - naive close, throws on the second call
await closeLivechatRoom(rid, user, { clientAction: true });

// after - idempotent close
try {
  await closeLivechatRoom(rid, user, { clientAction: true });
} catch (err: any) {
  if (err?.message !== 'error-room-already-closed') throw err;
  // already closed - nothing left to do
}
Defensive patterns

Strategy: type-guard

Validate before calling

const room = await LivechatRooms.findOneById(roomId);
if (!room?.open) {
  // nothing to close: return success (idempotent) or inform the caller the room is closed
}

Type guard

import { isOmnichannelRoom } from '@rocket.chat/core-typings';

const isCloseableOmnichannelRoom = (room: any, forceClose = false): room is IOmnichannelRoom =>
  !!room && isOmnichannelRoom(room) && (forceClose || room.open === true);

Try / catch

try {
  await closeLivechatRoom(roomId, user, { clientAction: true });
} catch (err: any) {
  if (err?.message === 'error-room-already-closed') return; // goal state already reached
  throw err;
}

Prevention

When it happens

Trigger: Calling closeLivechatRoom twice for the same rid - double click, client retry after a timeout, webhook re-delivery - without forceClose; or closing a room already closed by the other agent, a routing rule, or an inactivity auto-close.

Common situations: Retry logic without idempotency awareness; UI room state not refreshed after the first successful close; races between manual close and automatic closure (inactivity timeout, business hours end).

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/56a9640a71a66978. Report an issue: GitHub.