{"record":{"id":"db6ac8dc4dee07cb","repo":"RocketChat/Rocket.Chat","slug":"error-room-closed","errorCode":"error-room-closed","errorMessage":"error-room-closed","messagePattern":"error-room-closed","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/lib/omnichannel/closeRoom.ts","lineNumber":136,"sourceCode":"\tvoid notifyOnRoomChangedById(newRoom._id);\n\tif (inquiry) {\n\t\tvoid notifyOnLivechatInquiryChanged(inquiry, 'removed');\n\t}\n\n\tlogger.debug({ msg: 'Room was closed', roomId: newRoom._id });\n}\n\nasync function doCloseRoom(\n\tparams: CloseRoomParams,\n\tsession: ClientSession,\n): Promise<{ room: IOmnichannelRoom; closedBy: ChatCloser; removedInquiry: ILivechatInquiryRecord | null }> {\n\tconst { comment } = params;\n\tconst { room, forceClose } = params;\n\n\tlogger.debug({ msg: 'Attempting to close room', roomId: room._id, forceClose });\n\tif (!room || !isOmnichannelRoom(room) || (!forceClose && !room.open)) {\n\t\tlogger.debug({ msg: 'Room is not open', roomId: room._id });\n\t\tthrow new Error('error-room-closed');\n\t}\n\n\tconst commentRequired = settings.get('Livechat_request_comment_when_closing_conversation');\n\tif (commentRequired && !comment?.trim()) {\n\t\tthrow new Error('error-comment-is-required');\n\t}\n\n\tconst { updatedOptions: options } = await resolveChatTags(room, params.options);\n\tlogger.debug({ msg: 'Resolved chat tags for room', roomId: room._id });\n\n\tconst now = new Date();\n\tconst { _id: rid, servedBy } = room;\n\tconst serviceTimeDuration = servedBy && (now.getTime() - new Date(servedBy.ts).getTime()) / 1000;\n\n\tconst closeData: IOmnichannelRoomClosingInfo = {\n\t\tclosedAt: now,\n\t\tchatDuration: (now.getTime() - new Date(room.ts).getTime()) / 1000,\n\t\t...(serviceTimeDuration && { serviceTimeDuration }),","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/closeRoom.ts#L118-L154","documentation":"The inner doCloseRoom throws Error('error-room-closed') when the room object is missing, is not an omnichannel room (isOmnichannelRoom fails), or is not open while forceClose is unset. Unlike the outer 'error-room-already-closed', it also rejects rooms of the wrong type reaching the omnichannel close pipeline (e.g. a regular channel).","triggerScenarios":"Passing a non-omnichannel room (team channel, DM) into closeRoom; closing an omnichannel room that was already closed without forceClose; a race where the room got closed between being fetched and doCloseRoom running.","commonSituations":"Integrations that look rooms up by name and accidentally feed a non-livechat room into the close flow; shared close logic reused for regular channels; double-close races.","solutions":["Verify isOmnichannelRoom(room) && room.open before invoking closeRoom","Pass forceClose: true when intentionally re-closing an already-closed omnichannel room","Use the regular channel close APIs for non-omnichannel rooms instead of this pipeline"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"import { isOmnichannelRoom } from '@rocket.chat/core-typings';\n\nif (!room || !isOmnichannelRoom(room) || (!forceClose && !room.open)) {\n  // skip closeRoom: wrong room type or already closed\n}","typeGuard":"import { isOmnichannelRoom } from '@rocket.chat/core-typings';\n\nconst isClosableLivechatRoom = (room: any, forceClose = false): room is IOmnichannelRoom =>\n  !!room && isOmnichannelRoom(room) && (forceClose || room.open === true);","tryCatchPattern":"try {\n  await closeRoom({ room, user, comment });\n} catch (err: any) {\n  if (err?.message === 'error-room-closed') return alreadyClosed(room._id);\n  throw err;\n}","preventionTips":["Filter rooms through isOmnichannelRoom before feeding any omnichannel close pipeline","Keep regular-channel close logic separate from livechat close logic","Re-check room.open immediately before the close call in racy UIs"],"tags":["omnichannel","livechat","room","state","validation"],"backgroundTag":"invalid-room-state","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}