{"record":{"id":"d86cd311d99c5cf5","repo":"RocketChat/Rocket.Chat","slug":"error-room-already-closed","errorCode":null,"errorMessage":"error-room-already-closed","messagePattern":"error-room-already-closed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/local-services/omnichannel.internalService.ts","lineNumber":44,"sourceCode":"\tconstructor() {\n\t\tsuper();\n\t\tthis.logger = new Logger('OmnichannelEE');\n\t}\n\n\tasync placeRoomOnHold(\n\t\troom: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold'>,\n\t\tcomment: string,\n\t\tonHoldBy: Pick<IUser, '_id' | 'username' | 'name'>,\n\t) {\n\t\tthis.logger.debug({ msg: 'Attempting to place room on hold', roomId: room._id, userId: onHoldBy?._id });\n\n\t\tconst { _id: roomId } = room;\n\n\t\tif (!room || !isOmnichannelRoom(room)) {\n\t\t\tthrow new Error('error-invalid-room');\n\t\t}\n\t\tif (!room.open) {\n\t\t\tthrow new Error('error-room-already-closed');\n\t\t}\n\t\tif (room.onHold) {\n\t\t\tthrow new Error('error-room-is-already-on-hold');\n\t\t}\n\t\tconst restrictedOnHold = settings.get('Livechat_allow_manual_on_hold_upon_agent_engagement_only');\n\t\tconst canRoomBePlacedOnHold = !room.onHold;\n\t\tconst canAgentPlaceOnHold = !room.lastMessage?.token;\n\t\tconst canPlaceChatOnHold = canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold);\n\t\tif (!canPlaceChatOnHold) {\n\t\t\tthrow new Error('error-cannot-place-chat-on-hold');\n\t\t}\n\t\tif (!room.servedBy) {\n\t\t\tthrow new Error('error-unserved-rooms-cannot-be-placed-onhold');\n\t\t}\n\n\t\tconst [roomResult, subsResult] = await Promise.all([\n\t\t\tLivechatRooms.setOnHoldByRoomId(roomId),\n\t\t\tSubscriptions.setOnHoldByRoomId(roomId),","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/local-services/omnichannel.internalService.ts#L26-L62","documentation":"placeRoomOnHold only operates on open conversations: after the room-type guard, if room.open is false it throws Error('error-room-already-closed'). Holding a chat exists to pause a live conversation for later resume, which is meaningless once the room is closed.","triggerScenarios":"Calling placeRoomOnHold after the conversation was closed - the agent closed it, inactivity timeout closed it, or an API close raced the hold. Typical shape: stale client state letting the agent click 'on hold' on a chat that is already closed.","commonSituations":"Outdated room data in the client after a close event; two agents concurrently closing and holding the same chat; automation acting on an old room snapshot.","solutions":["Re-fetch the room and check open before invoking; disable the on-hold action for closed rooms in the UI.","Catch 'error-room-already-closed' and treat it as benign - the desired end state (chat not open) already holds.","Sequence automation so hold attempts cannot be enqueued after close requests."],"exampleFix":"// before\nawait service.placeRoomOnHold(staleRoom, comment, user); // staleRoom.open was true in the client only\n\n// after\nconst fresh = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });\nif (!fresh?.open) return; // already closed; nothing to hold\nawait service.placeRoomOnHold(fresh, comment, user);","handlingStrategy":"validation","validationCode":"const fresh = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });\nif (!fresh?.open) {\n\t// already closed; holding is not applicable - skip the call\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait service.placeRoomOnHold(room, comment, user);\n} catch (e: any) {\n\tif (e?.message === 'error-room-already-closed') return; // desired end state already holds; benign\n\tthrow e;\n}","preventionTips":["Re-fetch room.open immediately before hold operations.","Disable the hold action in the UI when the room is closed or on the close event.","Do not enqueue hold jobs from stale room snapshots."],"tags":["omnichannel","on-hold","room-state","enterprise"],"backgroundTag":"invalid-room-state","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}