RocketChat/Rocket.Chat · error · Meteor.Error
error-room-is-not-closed
error-room-is-not-closed
Error message
error-room-is-not-closed
What it means
The last guard in removeOmnichannelRoom: open omnichannel rooms cannot be removed, so room.open === true raises Meteor.Error('error-room-is-not-closed'). The conversation must be closed before its room data is deleted.
Source
Thrown at apps/meteor/server/lib/omnichannel/rooms.ts:283
callbacks.runAsync('livechat:afterReturnRoomAsInquiry', { room });
return true;
}
export async function removeOmnichannelRoom(rid: string) {
livechatLogger.debug({ msg: 'Deleting room', roomId: rid });
check(rid, String);
const room = await LivechatRooms.findOneById(rid);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room');
}
if (!isOmnichannelRoom(room)) {
throw new Meteor.Error('error-this-is-not-a-livechat-room');
}
if (room.open) {
throw new Meteor.Error('error-room-is-not-closed');
}
const inquiry = await LivechatInquiry.findOneByRoomId(rid);
const result = await Promise.allSettled([
Messages.removeByRoomId(rid),
ReadReceipts.removeByRoomId(rid),
Subscriptions.removeByRoomId(rid, {
async onTrash(doc) {
void notifyOnSubscriptionChanged(doc, 'removed');
},
}),
LivechatInquiry.removeByRoomId(rid),
LivechatRooms.removeById(rid),
ReadReceiptsArchive.removeByRoomId(rid),
]);
if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) {View on GitHub (pinned to b2c16d5842)
Solutions
- Close the conversation first (omnichannel close flow/API), then call removeOmnichannelRoom.
- Verify room.open === false immediately before deleting.
- If a close already ran, re-fetch the room to see the updated state before retrying.
Example fix
// before
await removeOmnichannelRoom(rid); // room still open -> error-room-is-not-closed
// after
const room = await LivechatRooms.findOneById(rid);
if (room?.open) {
await closeOmnichannelRoom({ room, user: closingUser });
}
await removeOmnichannelRoom(rid); Defensive patterns
Strategy: validation
Validate before calling
const room = await LivechatRooms.findOneById(rid, { projection: { _id: 1, open: 1 } });
if (room?.open) {
await closeOmnichannelRoom({ room, user: closingUser });
}
await removeOmnichannelRoom(rid); Type guard
const isClosableForRemoval = (room: Pick<IOmnichannelRoom, 'open'>): boolean => room.open !== true;
Try / catch
try {
await removeOmnichannelRoom(rid);
} catch (err) {
if (err instanceof Meteor.Error && err.error === 'error-room-is-not-closed') {
// close the conversation first, verify open === false, then retry once
return;
}
throw err;
} Prevention
- Always run the close flow before the delete flow; never assume close succeeded.
- Re-fetch room.open immediately before deletion to avoid races.
- In bulk cleanup jobs, process rooms in closed state only.
When it happens
Trigger: Attempting to delete an omnichannel room whose conversation is still open — e.g., automation deletes rooms right after creation, or the close call raced/failed silently before the delete.
Common situations: Cleanup scripts that delete conversation rooms without closing them first; a preceding close request failed but the error was swallowed; UI shows the room as closed while the server still has open=true.
Related errors
- room-closed
- error-room-onHold
- error-invalid-room
- error-this-is-not-a-livechat-room
- error-invalid-inquiry
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/738d0eb7dcb95c53.
Report an issue: GitHub.