{"record":{"id":"cdb835192b1c0e70","repo":"RocketChat/Rocket.Chat","slug":"this-conversation-is-already-closed-cdb835","errorCode":"This_conversation_is_already_closed","errorMessage":"This_conversation_is_already_closed","messagePattern":"This_conversation_is_already_closed","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/room.ts","lineNumber":300,"sourceCode":");\n\nAPI.v1.addRoute(\n\t'livechat/room.forward',\n\t{ authRequired: true, permissionsRequired: ['view-l-room', 'transfer-livechat-guest'], validateParams: isLiveChatRoomForwardProps },\n\t{\n\t\tasync post() {\n\t\t\tconst transferData = this.bodyParams as typeof this.bodyParams & {\n\t\t\t\ttransferredBy: TransferByData;\n\t\t\t\ttransferredTo?: { _id: string; username?: string; name?: string };\n\t\t\t};\n\n\t\t\tconst room = await LivechatRooms.findOneById(this.bodyParams.roomId);\n\t\t\tif (room?.t !== 'l') {\n\t\t\t\tthrow new Error('error-invalid-room');\n\t\t\t}\n\n\t\t\tif (!room.open) {\n\t\t\t\tthrow new Error('This_conversation_is_already_closed');\n\t\t\t}\n\n\t\t\tif (!(await Omnichannel.isWithinMACLimit(room))) {\n\t\t\t\tthrow new Error('error-mac-limit-reached');\n\t\t\t}\n\n\t\t\tconst guest = await LivechatVisitors.findOneEnabledById(room.v?._id);\n\t\t\tif (!guest) {\n\t\t\t\tthrow new Error('error-invalid-visitor');\n\t\t\t}\n\n\t\t\ttransferData.transferredBy = normalizeTransferredByData(this.user, room);\n\t\t\tif (transferData.userId) {\n\t\t\t\tconst userToTransfer = await Users.findOneById(transferData.userId);\n\t\t\t\tif (userToTransfer) {\n\t\t\t\t\ttransferData.transferredTo = {\n\t\t\t\t\t\t_id: userToTransfer._id,\n\t\t\t\t\t\tusername: userToTransfer.username,","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/omnichannel/room.ts#L282-L318","documentation":"Thrown by the POST livechat/room.forward endpoint when the target Livechat room exists but has room.open === false, meaning the conversation was already closed. Rocket.Chat blocks transfers on closed rooms because the agent assignment and routing pipeline only operates on active omnichannel sessions. The guard fires before MAC, visitor, and transfer logic so a closed conversation fails fast.","triggerScenarios":"POST /api/v1/v1/livechat/room.forward with a bodyParams.roomId that resolves to a Livechat room whose `open` field is false. Happens when the client holds a stale roomId after the room was closed via agent close, inactivity timeout, omnichannel callback, or a prior transfer that closed the source room.","commonSituations":"UI keeps a room list cached after an agent closes a chat and the user clicks 'transfer'; race between a close callback (e.g. webhook/timeout) and the transfer click; integration replaying a roomId from an earlier session; agent has the room open in a tab while another agent closes it.","solutions":["Before calling room.forward, fetch the room (GET livechat/rooms or livechat/room.open state) and verify room.open === true; abort the transfer UI if closed.","Handle this specific error in the client by refreshing the room list and showing 'Conversation already closed' instead of retrying.","If reopening is intended, call livechat/room.open first, then retry the forward.","Audit close callbacks/webhooks firing earlier than expected (e.g. Livechat_room_inactivity_period, Livechat_room_close_on_agent_offline)."],"exampleFix":"// before\nawait POST('/api/v1/livechat/room.forward', { roomId, userId });\n\n// after\nconst room = await GET('/api/v1/livechat/rooms', { query: JSON.stringify({ _id: roomId }) });\nif (!room.open) {\n  notifyUser('Conversation already closed');\n  return;\n}\nawait POST('/api/v1/livechat/room.forward', { roomId, userId });","handlingStrategy":"validation","validationCode":"const room = await LivechatRooms.findOneById(roomId, { projection: { open: 1, t: 1 } });\nif (!room || room.t !== 'l') throw new ClientError('invalid-room');\nif (!room.open) throw new ClientError('room-closed');\n// now safe to call POST livechat/room.forward","typeGuard":"const isForwardableRoom = (r: unknown): r is { open: true; t: 'l'; _id: string } =>\n  !!r && typeof r === 'object' && (r as any).t === 'l' && (r as any).open === true && typeof (r as any)._id === 'string';","tryCatchPattern":"try {\n  await POST('/api/v1/livechat/room.forward', { roomId, userId });\n} catch (e) {\n  if (e.message === 'This_conversation_is_already_closed') { refreshRoomList(); notifyClosed(); return; }\n  throw e;\n}","preventionTips":["Refresh the room's open state before exposing transfer/cancel actions in the UI.","Treat close callbacks (webhooks, inactivity) as authoritative and invalidate cached room state.","Disable the transfer button immediately when the local room.open flag flips to false."],"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"}