{"record":{"id":"d4ae6375bfb8243c","repo":"RocketChat/Rocket.Chat","slug":"room-closed-d4ae63","errorCode":"room-closed","errorMessage":"room-closed","messagePattern":"room-closed","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/omnichannel/rooms.ts","lineNumber":223,"sourceCode":"\n\t\tif (responses[1]?.modifiedCount) {\n\t\t\tvoid notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name });\n\t\t}\n\n\t\tif (responses[2]?.modifiedCount) {\n\t\t\tawait notifyOnSubscriptionChangedByRoomId(rid);\n\t\t}\n\t}\n\n\tvoid notifyOnRoomChangedById(roomData._id);\n\n\treturn true;\n}\n\nexport async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: string, overrideTransferData: Partial<TransferData> = {}) {\n\tlivechatLogger.debug({ msg: 'Transferring room to queue', scope: departmentId ? 'department' : undefined, room });\n\tif (!room.open) {\n\t\tthrow new Meteor.Error('room-closed', 'Room closed');\n\t}\n\n\tif (room.onHold) {\n\t\tthrow new Meteor.Error('error-room-onHold');\n\t}\n\n\tif (!(await Omnichannel.isWithinMACLimit(room))) {\n\t\tthrow new Meteor.Error('error-mac-limit-reached');\n\t}\n\n\tif (!room.servedBy) {\n\t\treturn false;\n\t}\n\n\tconst user = await Users.findOneById(room.servedBy._id);\n\tif (!user?._id) {\n\t\tthrow new Meteor.Error('error-invalid-user');\n\t}","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/rooms.ts#L205-L241","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait returnRoomAsInquiry(room, departmentId); // throws room-closed\n\n// after\nif (!room.open) {\n\t// nothing to return - conversation already closed\n\treturn false;\n}\nawait returnRoomAsInquiry(room, departmentId);","handlingStrategy":"validation","validationCode":"if (!room.open) {\n\t// conversation already closed - nothing to return to the queue\n\treturn false;\n}\nawait returnRoomAsInquiry(room, departmentId);","typeGuard":"const isReturnableToQueue = (room: Pick<IOmnichannelRoom, 'open' | 'onHold'>): boolean =>\n\troom.open === true && room.onHold !== true;","tryCatchPattern":"try {\n\tawait returnRoomAsInquiry(room, departmentId);\n} catch (err) {\n\tif (err instanceof Meteor.Error && err.error === 'room-closed') {\n\t\t// treat as already-done for idempotent transfer workflows\n\t\treturn;\n\t}\n\tthrow err;\n}","preventionTips":["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."],"tags":["omnichannel","livechat","rooms","queue","invalid-state"],"backgroundTag":"invalid-room-state","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}