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
- Check the room is open (re-fetch it) before initiating the transfer; abort if closed.
- Make transfer handlers idempotent: treat 'room-closed' as already-done rather than an error.
- 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
- Re-fetch the room immediately before transferring; do not trust a UI-cached copy.
- Handle close/transfer races explicitly in agent tooling.
- Treat room-closed as a benign outcome in retries, not a hard failure.
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.