{"record":{"id":"738d0eb7dcb95c53","repo":"RocketChat/Rocket.Chat","slug":"error-room-is-not-closed","errorCode":"error-room-is-not-closed","errorMessage":"error-room-is-not-closed","messagePattern":"error-room-is-not-closed","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/omnichannel/rooms.ts","lineNumber":283,"sourceCode":"\tcallbacks.runAsync('livechat:afterReturnRoomAsInquiry', { room });\n\n\treturn true;\n}\n\nexport async function removeOmnichannelRoom(rid: string) {\n\tlivechatLogger.debug({ msg: 'Deleting room', roomId: rid });\n\tcheck(rid, String);\n\tconst room = await LivechatRooms.findOneById(rid);\n\tif (!room) {\n\t\tthrow new Meteor.Error('error-invalid-room', 'Invalid room');\n\t}\n\n\tif (!isOmnichannelRoom(room)) {\n\t\tthrow new Meteor.Error('error-this-is-not-a-livechat-room');\n\t}\n\n\tif (room.open) {\n\t\tthrow new Meteor.Error('error-room-is-not-closed');\n\t}\n\n\tconst inquiry = await LivechatInquiry.findOneByRoomId(rid);\n\n\tconst result = await Promise.allSettled([\n\t\tMessages.removeByRoomId(rid),\n\t\tReadReceipts.removeByRoomId(rid),\n\t\tSubscriptions.removeByRoomId(rid, {\n\t\t\tasync onTrash(doc) {\n\t\t\t\tvoid notifyOnSubscriptionChanged(doc, 'removed');\n\t\t\t},\n\t\t}),\n\t\tLivechatInquiry.removeByRoomId(rid),\n\t\tLivechatRooms.removeById(rid),\n\t\tReadReceiptsArchive.removeByRoomId(rid),\n\t]);\n\n\tif (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) {","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/rooms.ts#L265-L301","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait removeOmnichannelRoom(rid); // room still open -> error-room-is-not-closed\n\n// after\nconst room = await LivechatRooms.findOneById(rid);\nif (room?.open) {\n\tawait closeOmnichannelRoom({ room, user: closingUser });\n}\nawait removeOmnichannelRoom(rid);","handlingStrategy":"validation","validationCode":"const room = await LivechatRooms.findOneById(rid, { projection: { _id: 1, open: 1 } });\nif (room?.open) {\n\tawait closeOmnichannelRoom({ room, user: closingUser });\n}\nawait removeOmnichannelRoom(rid);","typeGuard":"const isClosableForRemoval = (room: Pick<IOmnichannelRoom, 'open'>): boolean => room.open !== true;","tryCatchPattern":"try {\n\tawait removeOmnichannelRoom(rid);\n} catch (err) {\n\tif (err instanceof Meteor.Error && err.error === 'error-room-is-not-closed') {\n\t\t// close the conversation first, verify open === false, then retry once\n\t\treturn;\n\t}\n\tthrow err;\n}","preventionTips":["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."],"tags":["omnichannel","livechat","rooms","deletion","invalid-state"],"backgroundTag":"invalid-room-state","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}