{"record":{"id":"298e9a52e1f672c0","repo":"RocketChat/Rocket.Chat","slug":"room-closed-298e9a","errorCode":null,"errorMessage":"room-closed","messagePattern":"room-closed","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"warning","filePath":"apps/meteor/server/api/v1/omnichannel/room.ts","lineNumber":152,"sourceCode":"\t\tasync post() {\n\t\t\tconst { rid, token } = this.bodyParams;\n\n\t\t\tif (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {\n\t\t\t\tthrow new Error('error-not-allowed-to-close-conversation');\n\t\t\t}\n\n\t\t\tconst visitor = await findGuest(token);\n\t\t\tif (!visitor) {\n\t\t\t\tthrow new Error('invalid-token');\n\t\t\t}\n\n\t\t\tconst room = await findRoom(token, rid);\n\t\t\tif (!room) {\n\t\t\t\tthrow new Error('invalid-room');\n\t\t\t}\n\n\t\t\tif (!room.open) {\n\t\t\t\tthrow new Error('room-closed');\n\t\t\t}\n\n\t\t\tconst language = rcSettings.get<string>('Language') || 'en';\n\t\t\tconst comment = i18n.t('Closed_by_visitor', { lng: language });\n\n\t\t\tconst options: CloseRoomParams['options'] = {};\n\t\t\tif (room.servedBy) {\n\t\t\t\tconst servingAgent: Pick<IUser, '_id' | 'name' | 'username' | 'utcOffset' | 'settings' | 'language'> | null =\n\t\t\t\t\tawait Users.findOneById(room.servedBy._id, {\n\t\t\t\t\t\tprojection: {\n\t\t\t\t\t\t\tname: 1,\n\t\t\t\t\t\t\tusername: 1,\n\t\t\t\t\t\t\tutcOffset: 1,\n\t\t\t\t\t\t\tsettings: 1,\n\t\t\t\t\t\t\tlanguage: 1,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/room.ts#L134-L170","documentation":"Thrown by POST /api/v1/livechat/room.close when the room was found (rid+token matched) but room.open is false — i.e. the visitor is trying to close a conversation that is already closed. The lookup findRoom does not filter on open state, so closed rooms reach this check and are rejected here.","triggerScenarios":"Visitor clicks 'close chat' twice (double submit, retry after timeout); widget UI kept a rid after the conversation was already closed by the agent, by a timeout, or by a previous close request that the client did not register as successful.","commonSituations":"Duplicate close buttons / racing requests in custom widget builds; agent closed the room while the visitor had the widget open; offline/async widget closes room server-side and visitor returns to a stale session.","solutions":["Treat this response as success in the widget: the desired end state (room closed) already holds — check room state before showing an error","Fetch current state first: GET /api/v1/livechat/room?token=... returns only an open room; if it creates/returns nothing open, skip the close call","Guard the UI against double submission (disable button after first click)"],"exampleFix":"// before\nif (await closeRoom(rid, token)) hideChatWindow(); // throws room-closed on second click\n\n// after\nconst res = await closeRoom(rid, token).catch((e) => e);\nif (res instanceof Error && res.message === 'room-closed') {\n  hideChatWindow(); // already closed — same end state, not a failure\n} else if (res) {\n  hideChatWindow();\n}","handlingStrategy":"fallback","validationCode":"const room = await resolveOpenRoom(url, token);\nif (!room || room.open === false) { markConversationClosedLocally(); return; }","typeGuard":null,"tryCatchPattern":"try { await closeRoom(rid, token); } catch (e) { if (e.message === 'room-closed') return markConversationClosedLocally(); throw e; }","preventionTips":["Disable the close button after first click","Treat room-closed as the success end-state for idempotent close flows","Track room.open in widget state from websocket events, not just API responses"],"tags":["omnichannel","livechat","room-state","idempotency","rest-api"],"backgroundTag":"invalid-room-state","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"}