{"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/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/local-services/omnichannel.internalService.ts#L76-L112","documentation":"Thrown by resumeRoomOnHold when `room.onHold` is false. Resume is only meaningful for a held chat; calling it on a chat that is not on hold is a no-op that the service rejects. The check runs after the open check and before reading servedBy.","triggerScenarios":"Calling resumeRoomOnHold on a room whose onHold is false (an active or queued chat that was never held, or one already resumed by another action).","commonSituations":"Double resume (user clicks Resume twice); a held room was auto-resumed by a visitor message and the agent's stale UI still shows it as held.","solutions":["Drive the Resume button off room.onHold === true and disable it after the first click.","Re-fetch the room before resume and treat onHold === false as 'already active' rather than erroring.","Listen for the room-changed notification that flips onHold and update the UI immediately."],"exampleFix":"// before\nawait omnichannelService.resumeRoomOnHold(room, comment, user);\n\n// after\nif (!room.onHold) {\n  return { alreadyActive: true };\n}\nawait omnichannelService.resumeRoomOnHold(room, comment, user);","handlingStrategy":"validation","validationCode":"if (!room.onHold) return { alreadyActive: true };\nawait omnichannelService.resumeRoomOnHold(room, comment, user);","typeGuard":"function isHeldRoom(room: unknown): boolean {\n  return !!room && (room as any).onHold === true;\n}","tryCatchPattern":"try {\n  await omnichannelService.resumeRoomOnHold(room, comment, user);\n} catch (e) {\n  if (e instanceof Error && e.message === 'error-room-not-on-hold') return;\n  throw e;\n}","preventionTips":["Drive Resume off room.onHold === true and disable after the first click.","Treat onHold === false as 'already active'.","Listen for room-change notifications to keep onHold state fresh."],"tags":["omnichannel","livechat","room-state","idempotency"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}