{"record":{"id":"105cb716d6d4f2e4","repo":"RocketChat/Rocket.Chat","slug":"error-room-not-on-hold","errorCode":null,"errorMessage":"error-room-not-on-hold","messagePattern":"error-room-not-on-hold","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/local-services/omnichannel.internalService.ts","lineNumber":94,"sourceCode":"\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}\n\n\t\tawait this.attemptToAssignRoomToServingAgentElseQueueIt({\n\t\t\troom,\n\t\t\tinquiry,","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/local-services/omnichannel.internalService.ts#L76-L112","documentation":"Thrown by resumeRoomOnHold (omnichannel.internalService.ts:94) when the room passed to the resume flow does not have onHold truthy. The method only resumes conversations that are currently on hold, so a room that was never held or was already resumed fails this precondition before any database change.","triggerScenarios":"Calling resumeRoomOnHold with a room whose onHold is false or undefined — e.g. double-resume (second click after the first succeeded), a room never placed on hold, or a stale room object fetched before another flow resumed it.","commonSituations":"Duplicate 'resume' submissions from the client; stale room state after the on-hold flag was cleared by a concurrent resume; automation/server code resuming rooms that are simply open, not on hold.","solutions":["Re-fetch the room right before resuming and verify room.onHold === true","Make the resume action idempotent in the UI: once a resume succeeds, remove the on-hold state from local room and disable the action","Guard server-side callers with an explicit onHold check so only genuinely held rooms are resumed"],"exampleFix":"// before\nawait omnichannelService.resumeRoomOnHold(room, comment, agent);\n\n// after\nif (!room.onHold) {\n\t// nothing to resume — treat as success or notify the user\n\treturn;\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?.onHold) {\n\t// nothing to resume — chat is not on hold\n\treturn;\n}\nawait omnichannelService.resumeRoomOnHold(room, comment, agent);","typeGuard":"const isRoomOnHold = (room: IOmnichannelRoom | null | undefined): room is IOmnichannelRoom & { onHold: true } =>\n\t!!room && isOmnichannelRoom(room) && room.onHold === true;","tryCatchPattern":"try {\n\tawait omnichannelService.resumeRoomOnHold(room, comment, agent);\n} catch (e) {\n\tif (e instanceof Error && e.message === 'error-room-not-on-hold') {\n\t\t// treat as already-resumed: refresh room state, no retry\n\t}\n\tthrow e;\n}","preventionTips":["Make the resume action idempotent: after a successful resume clear the local onHold flag and disable the button","Pre-check room.onHold on freshly fetched data before invoking the service","Debounce double-clicks on the resume control"],"tags":["omnichannel","livechat","on-hold","state-precondition","idempotency"],"backgroundTag":"invalid-state-transition","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"}