{"record":{"id":"ff0c04bcf762e82a","repo":"RocketChat/Rocket.Chat","slug":"this-conversation-is-already-closed","errorCode":null,"errorMessage":"This_conversation_is_already_closed","messagePattern":"This_conversation_is_already_closed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/local-services/omnichannel.internalService.ts","lineNumber":90,"sourceCode":"\t\t}\n\n\t\tawait callbacks.run('livechat:afterOnHold', room);\n\t}\n\n\tasync resumeRoomOnHold(\n\t\troom: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy'>,\n\t\tcomment: string,\n\t\tresumeBy: Pick<IUser, '_id' | 'username' | 'name'>,\n\t\tclientAction = false,\n\t) {\n\t\tthis.logger.debug({ msg: 'Attempting to resume room on hold', roomId: room._id, userId: resumeBy?._id });\n\n\t\tif (!room || !isOmnichannelRoom(room)) {\n\t\t\tthrow new Error('error-invalid-room');\n\t\t}\n\n\t\tif (!room.open) {\n\t\t\tthrow new Error('This_conversation_is_already_closed');\n\t\t}\n\n\t\tif (!room.onHold) {\n\t\t\tthrow new Error('error-room-not-on-hold');\n\t\t}\n\n\t\tconst { _id: roomId, servedBy } = room;\n\n\t\tif (!servedBy) {\n\t\t\tthis.logger.error({ msg: 'No serving agent found for room', roomId });\n\t\t\tthrow new Error('error-room-not-served');\n\t\t}\n\n\t\tconst inquiry = await LivechatInquiry.findOneByRoomId(roomId, {});\n\t\tif (!inquiry) {\n\t\t\tthis.logger.error({ msg: 'No inquiry found for room', roomId });\n\t\t\tthrow new Error('error-invalid-inquiry');\n\t\t}","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/local-services/omnichannel.internalService.ts#L72-L108","documentation":"Thrown by OmnichannelInternalService.resumeRoomOnHold (apps/meteor/ee/server/local-services/omnichannel.internalService.ts:90) when an agent tries to resume an on-hold Omnichannel conversation whose room document has open === false. The method validates room state before mutating anything, so this means the conversation was already closed (by the visitor, another agent, auto-close, or REST close) and cannot be resumed.","triggerScenarios":"Calling resumeRoomOnHold(room, comment, resumeBy) — from the client 'resume on hold' action or server code — with a room object whose open flag is false. Typically the caller holds a stale room fetched before the room was closed elsewhere, or a close and resume race each other.","commonSituations":"Agent UI keeps stale room state after the conversation was closed in another tab/agent client or via REST; Livechat auto-close closed the on-hold chat; user re-triggers an old 'resume' action after the chat ended.","solutions":["Re-fetch the room by _id (LivechatRooms.findOneById) immediately before calling resumeRoomOnHold and abort if open is false","In the UI, disable or hide the 'resume' action when room.open is false and show a 'conversation closed' notice","If close and resume race, catch this error, re-read the room, and report 'already closed' instead of retrying","Check whether on-hold auto-close settings (Livechat chat closing timer for on-hold conversations) closed the room and adjust the workflow"],"exampleFix":"// before\nconst room = await LivechatRooms.findOneById(rid);\nawait omnichannelService.resumeRoomOnHold(room, comment, agent);\n\n// after\nconst room = await LivechatRooms.findOneById(rid);\nif (!room?.open) {\n\tthrow new Error('This_conversation_is_already_closed');\n}\nawait omnichannelService.resumeRoomOnHold(room, comment, agent);","handlingStrategy":"validation","validationCode":"const room = await LivechatRooms.findOneById(rid, { projection: { t: 1, open: 1, onHold: 1, servedBy: 1 } });\nif (!room || !isOmnichannelRoom(room) || !room.open) {\n\t// conversation closed — do not attempt resume\n\tthrow new Error('This_conversation_is_already_closed');\n}\nawait omnichannelService.resumeRoomOnHold(room, comment, agent);","typeGuard":"const isResumableOnHoldRoom = (\n\troom: IOmnichannelRoom | null | undefined,\n): room is IOmnichannelRoom & { open: true; onHold: true; servedBy: NonNullable<IOmnichannelRoom['servedBy']> } =>\n\t!!room && isOmnichannelRoom(room) && room.open === true && room.onHold === true && !!room.servedBy;","tryCatchPattern":"try {\n\tawait omnichannelService.resumeRoomOnHold(room, comment, agent);\n} catch (e) {\n\tif (e instanceof Error && e.message === 'This_conversation_is_already_closed') {\n\t\t// re-sync room state and inform the user; do not retry\n\t}\n\tthrow e;\n}","preventionTips":["Always re-fetch the room immediately before resume-on-hold instead of trusting client state","Disable the resume action in the UI as soon as room.open turns false","Review on-hold auto-close timers so agents are not resuming chats a timer already closed"],"tags":["omnichannel","livechat","on-hold","room-state","race-condition"],"backgroundTag":"operation-on-closed-resource","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"}