RocketChat/Rocket.Chat · error · Error

room-closed

room-closed

Error message

room-closed

What it means

Thrown in the POST handler of 'livechat/room.close' (room.ts:151-153) when room.open is false. After successfully finding the room by token and rid, the handler checks whether the room is still open. If the room has already been closed (by visitor, agent, or system), room.open is false and this error fires.

Source

Thrown at apps/meteor/server/api/v1/omnichannel/room.ts:152

		async post() {
			const { rid, token } = this.bodyParams;

			if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {
				throw new Error('error-not-allowed-to-close-conversation');
			}

			const visitor = await findGuest(token);
			if (!visitor) {
				throw new Error('invalid-token');
			}

			const room = await findRoom(token, rid);
			if (!room) {
				throw new Error('invalid-room');
			}

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

			const language = rcSettings.get<string>('Language') || 'en';
			const comment = i18n.t('Closed_by_visitor', { lng: language });

			const options: CloseRoomParams['options'] = {};
			if (room.servedBy) {
				const servingAgent: Pick<IUser, '_id' | 'name' | 'username' | 'utcOffset' | 'settings' | 'language'> | null =
					await Users.findOneById(room.servedBy._id, {
						projection: {
							name: 1,
							username: 1,
							utcOffset: 1,
							settings: 1,
							language: 1,
						},
					});

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Check the room's open status before attempting to close: db.livechat_rooms.findOne({_id: rid}, {open: 1}).
  2. If already closed, treat it as a no-op success — the desired end state is achieved.
  3. Disable the close button client-side after the first successful close to prevent double-submits.

Example fix

// before
await api.post('/livechat/room.close', { rid, token });
// throws 'room-closed' on second click

// after — guard against double-close client-side
if (roomAlreadyClosed) {
  return { success: true, message: 'Room already closed' };
}
await api.post('/livechat/room.close', { rid, token });
Defensive patterns

Strategy: type-guard

Validate before calling

// Check room.open before attempting to close
const room = await LivechatRooms.findOneByIdAndVisitorToken(rid, token);
if (room && !room.open) {
  // already closed — no action needed
  return { success: true, reason: 'already-closed' };
}

Type guard

function isRoomOpen(room: IOmnichannelRoom | null): room is IOmnichannelRoom {
  return room !== null && room.open === true;
}

Try / catch

try {
  await api.post('/livechat/room.close', { rid, token });
} catch (err) {
  if (err.message === 'room-closed') {
    // already closed — treat as success
    return { success: true };
  }
}

Prevention

When it happens

Trigger: Calling POST /api/v1/livechat/room.close for a room that is already in a closed state. This can happen on a double-close attempt or when an agent closed the room between the client's last status check and the close request.

Common situations: Visitor clicks close twice (double-submit); agent closed the room concurrently; the room was auto-closed by an inactivity timeout or system process; the client didn't refresh room state after a previous close.

Related errors


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