RocketChat/Rocket.Chat · error · Meteor.Error

room-closed

room-closed

Error message

room-closed

What it means

returnRoomAsInquiry transfers a served omnichannel room back to the queue. Its first guard refuses closed rooms with Meteor.Error('room-closed', 'Room closed') — only open rooms can be returned as an inquiry.

Source

Thrown at apps/meteor/server/lib/omnichannel/rooms.ts:223

		if (responses[1]?.modifiedCount) {
			void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });
		}

		if (responses[2]?.modifiedCount) {
			await notifyOnSubscriptionChangedByRoomId(rid);
		}
	}

	void notifyOnRoomChangedById(roomData._id);

	return true;
}

export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: string, overrideTransferData: Partial<TransferData> = {}) {
	livechatLogger.debug({ msg: 'Transferring room to queue', scope: departmentId ? 'department' : undefined, room });
	if (!room.open) {
		throw new Meteor.Error('room-closed', 'Room closed');
	}

	if (room.onHold) {
		throw new Meteor.Error('error-room-onHold');
	}

	if (!(await Omnichannel.isWithinMACLimit(room))) {
		throw new Meteor.Error('error-mac-limit-reached');
	}

	if (!room.servedBy) {
		return false;
	}

	const user = await Users.findOneById(room.servedBy._id);
	if (!user?._id) {
		throw new Meteor.Error('error-invalid-user');
	}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Check the room is open (re-fetch it) before initiating the transfer; abort if closed.
  2. Make transfer handlers idempotent: treat 'room-closed' as already-done rather than an error.
  3. If reopening-to-transfer is intended, reopen the conversation first, then return it to the queue.

Example fix

// before
await returnRoomAsInquiry(room, departmentId); // throws room-closed

// after
if (!room.open) {
	// nothing to return - conversation already closed
	return false;
}
await returnRoomAsInquiry(room, departmentId);
Defensive patterns

Strategy: validation

Validate before calling

if (!room.open) {
	// conversation already closed - nothing to return to the queue
	return false;
}
await returnRoomAsInquiry(room, departmentId);

Type guard

const isReturnableToQueue = (room: Pick<IOmnichannelRoom, 'open' | 'onHold'>): boolean =>
	room.open === true && room.onHold !== true;

Try / catch

try {
	await returnRoomAsInquiry(room, departmentId);
} catch (err) {
	if (err instanceof Meteor.Error && err.error === 'room-closed') {
		// treat as already-done for idempotent transfer workflows
		return;
	}
	throw err;
}

Prevention

When it happens

Trigger: Transferring/returning a room whose open flag is false — the conversation was closed between the UI action and the server call, or automation retries a transfer after closure.

Common situations: Race between an agent closing a chat and another agent/action transferring it; webhooks retried after the room closed; queued jobs that assume a room is still open.

Related errors


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