RocketChat/Rocket.Chat · warning · Error

room-closed

Error message

room-closed

What it means

Thrown by POST /api/v1/livechat/room.close when the room was found (rid+token matched) but room.open is false — i.e. the visitor is trying to close a conversation that is already closed. The lookup findRoom does not filter on open state, so closed rooms reach this check and are rejected here.

Solutions

  1. Treat this response as success in the widget: the desired end state (room closed) already holds — check room state before showing an error
  2. Fetch current state first: GET /api/v1/livechat/room?token=... returns only an open room; if it creates/returns nothing open, skip the close call
  3. Guard the UI against double submission (disable button after first click)

Example fix

// before
if (await closeRoom(rid, token)) hideChatWindow(); // throws room-closed on second click

// after
const res = await closeRoom(rid, token).catch((e) => e);
if (res instanceof Error && res.message === 'room-closed') {
  hideChatWindow(); // already closed — same end state, not a failure
} else if (res) {
  hideChatWindow();
}
Defensive patterns

Strategy: fallback

Validate before calling

const room = await resolveOpenRoom(url, token);
if (!room || room.open === false) { markConversationClosedLocally(); return; }

Try / catch

try { await closeRoom(rid, token); } catch (e) { if (e.message === 'room-closed') return markConversationClosedLocally(); throw e; }

Prevention

When it happens

Trigger: Visitor clicks 'close chat' twice (double submit, retry after timeout); widget UI kept a rid after the conversation was already closed by the agent, by a timeout, or by a previous close request that the client did not register as successful.

Common situations: Duplicate close buttons / racing requests in custom widget builds; agent closed the room while the visitor had the widget open; offline/async widget closes room server-side and visitor returns to a stale session.

Related errors


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

Appendix: 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 b2c16d5842)