RocketChat/Rocket.Chat · warning · Error
error-room-already-closed
error-room-already-closed
Error message
error-room-already-closed
What it means
The outer closeLivechatRoom wrapper throws Error('error-room-already-closed') when the loaded room has open=false and forceClose was not requested. Its purpose is to stop double-closing an omnichannel room; passing forceClose: true bypasses it and re-runs the close routine.
Source
Thrown at apps/meteor/server/lib/omnichannel/closeLivechatRoom.ts:78
emailTranscript: {
sendToVisitor: false,
},
}),
}),
};
if (forceClose) {
return closeRoom({
room,
user,
options,
comment,
forceClose,
});
}
if (!room.open) {
throw new Error('error-room-already-closed');
}
return closeRoom({
room,
user,
options,
comment,
});
};
View on GitHub (pinned to b2c16d5842)
Solutions
- Treat the error as success in idempotent close flows - the desired end state is already reached
- Guard the close control against double submission and refresh room state after closing succeeds
- Pass forceClose: true only when re-closing an already-closed room is genuinely intended (e.g. correcting closing metadata)
Example fix
// before - naive close, throws on the second call
await closeLivechatRoom(rid, user, { clientAction: true });
// after - idempotent close
try {
await closeLivechatRoom(rid, user, { clientAction: true });
} catch (err: any) {
if (err?.message !== 'error-room-already-closed') throw err;
// already closed - nothing left to do
} Defensive patterns
Strategy: type-guard
Validate before calling
const room = await LivechatRooms.findOneById(roomId);
if (!room?.open) {
// nothing to close: return success (idempotent) or inform the caller the room is closed
} Type guard
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
const isCloseableOmnichannelRoom = (room: any, forceClose = false): room is IOmnichannelRoom =>
!!room && isOmnichannelRoom(room) && (forceClose || room.open === true); Try / catch
try {
await closeLivechatRoom(roomId, user, { clientAction: true });
} catch (err: any) {
if (err?.message === 'error-room-already-closed') return; // goal state already reached
throw err;
} Prevention
- Disable the close control once room.open flips false; refresh room state after each close
- Make API/webhook close handlers idempotent by swallowing 'already closed'
- Reserve forceClose for deliberate re-close operations (e.g. correcting closing metadata)
When it happens
Trigger: Calling closeLivechatRoom twice for the same rid - double click, client retry after a timeout, webhook re-delivery - without forceClose; or closing a room already closed by the other agent, a routing rule, or an inactivity auto-close.
Common situations: Retry logic without idempotency awareness; UI room state not refreshed after the first successful close; races between manual close and automatic closure (inactivity timeout, business hours end).
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/56a9640a71a66978.
Report an issue: GitHub.