RocketChat/Rocket.Chat · warning · Error
room-closed
Error message
room-closed
What it means
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.
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)
Example fix
// before
if (await closeRoom(rid, token)) hideChatWindow(); // throws room-closed on second click
// after
const res = await closeRoom(rid, token).catch((e) => e);
if (res instanceof Error && res.message === 'room-closed') {
hideChatWindow(); // already closed — same end state, not a failure
} else if (res) {
hideChatWindow();
} Defensive patterns
Strategy: fallback
Validate before calling
const room = await resolveOpenRoom(url, token);
if (!room || room.open === false) { markConversationClosedLocally(); return; } Try / catch
try { await closeRoom(rid, token); } catch (e) { if (e.message === 'room-closed') return markConversationClosedLocally(); throw e; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/298e9a52e1f672c0.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/api/v1/omnichannel/room.ts:152
async post() {
const { rid, token } = this.bodyParams;
if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {
throw new Error('error-not-allowed-to-close-conversation');
}
const visitor = await findGuest(token);
if (!visitor) {
throw new Error('invalid-token');
}
const room = await findRoom(token, rid);
if (!room) {
throw new Error('invalid-room');
}
if (!room.open) {
throw new Error('room-closed');
}
const language = rcSettings.get<string>('Language') || 'en';
const comment = i18n.t('Closed_by_visitor', { lng: language });
const options: CloseRoomParams['options'] = {};
if (room.servedBy) {
const servingAgent: Pick<IUser, '_id' | 'name' | 'username' | 'utcOffset' | 'settings' | 'language'> | null =
await Users.findOneById(room.servedBy._id, {
projection: {
name: 1,
username: 1,
utcOffset: 1,
settings: 1,
language: 1,
},
});
View on GitHub (pinned to b2c16d5842)