{"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/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/local-services/omnichannel.internalService.ts#L26-L62","documentation":"Thrown by OmnichannelInternalService.placeRoomOnHold when the target room's `open` flag is falsy. The service refuses to put a closed conversation on hold because on-hold is a state that only applies to active (open) chats. The guard runs after the room-type check and before any hold-specific logic.","triggerScenarios":"Calling the on-hold flow (livechat onHold method / REST route that delegates to placeRoomOnHold) for a room whose `open` field is false in the LivechatRooms collection, e.g. a chat that was already closed via /livechat/room.close or that auto-closed.","commonSituations":"Stale client UI that still shows a 'Place on hold' button after the room was closed by another agent or by a visitor inactivity timeout; race where two agents act on the same room; cached room document passed into the service after a close.","solutions":["Refresh the room from LivechatRooms before offering the on-hold action and hide/disable the control when room.open === false.","If you call placeRoomOnHold directly, pre-check `room.open` and surface 'room is closed' to the user instead of letting the server throw.","Investigate why the room reached your code in a closed state (close-on-abandon, transfer, or another concurrent close call)."],"exampleFix":"// before\nawait omnichannelService.placeRoomOnHold(room, comment, user);\n\n// after\nif (!room.open) {\n  throw new Error('Room is closed; cannot place on hold');\n}\nawait omnichannelService.placeRoomOnHold(room, comment, user);","handlingStrategy":"validation","validationCode":"const room = await LivechatRooms.findOneById(roomId, { projection: { open: 1, t: 1 } });\nif (!room || !isOmnichannelRoom(room)) throw new Error('Invalid omnichannel room');\nif (!room.open) throw new Error('Room is closed; cannot place on hold');\n// safe to call placeRoomOnHold","typeGuard":"function canPlaceOnHold(room: unknown): room is IOmnichannelRoom {\n  return !!room && typeof room === 'object'\n    && (room as any).t === 'l'\n    && (room as any).open === true;\n}","tryCatchPattern":"try {\n  await omnichannelService.placeRoomOnHold(room, comment, user);\n} catch (e) {\n  if (e instanceof Error && e.message === 'error-room-already-closed') {\n    notifyUser('This conversation is already closed.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Drive the Hold button visibility off room.open === true from the live room document.","Disable action controls while a room request is in flight to avoid stale-state calls.","Subscribe to room-change notifications so the UI reflects closes immediately."],"tags":["omnichannel","livechat","room-state","validation"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}