RocketChat/Rocket.Chat · warning · Error
error-room-onHold
error-room-onHold
Error message
error-room-onHold
What it means
The omnichannel transfer() helper refuses to transfer a room whose onHold flag is set, throwing Error('error-room-onHold'). On-hold rooms are conversations paused waiting for the visitor to return; transferring them would break the hold semantics, so they must be resumed or closed first.
Source
Thrown at apps/meteor/server/lib/omnichannel/transfer.ts:69
const { _id, username, name } = user;
for await (const room of LivechatRooms.findOpenByAgent(userId)) {
const guest = await LivechatVisitors.findOneEnabledById(room.v._id);
if (!guest) {
continue;
}
const transferredBy = normalizeTransferredByData({ _id, username, name }, room);
await transfer(room, guest, {
transferredBy,
departmentId: guest.department,
});
}
}
export async function transfer(room: IOmnichannelRoom, guest: ILivechatVisitor, transferData: TransferData) {
livechatLogger.debug({ msg: 'Transferring room', roomId: room._id, transferredBy: transferData?.transferredBy?._id });
if (room.onHold) {
throw new Error('error-room-onHold');
}
if (transferData.departmentId) {
const department = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'name' | '_id'>>(transferData.departmentId, {
projection: { name: 1 },
});
if (!department) {
throw new Error('error-invalid-department');
}
transferData.department = department;
livechatLogger.debug({ msg: 'Transferring room to department', roomId: room._id, departmentId: transferData.department?._id });
}
return RoutingManager.transferRoom(room, guest, transferData);
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Resume the room first (take it off hold / send a message so it becomes active again), then transfer
- Or close the on-hold conversation instead of transferring it if it's stale
- In transfer UIs, detect room.onHold and offer resume-or-close instead of transfer
- Audit automation that transfers rooms to skip onHold === true rooms
Example fix
// before
await transfer(room, guest, transferData); // throws error-room-onHold
// after
if (room.onHold) {
throw new Meteor.Error('error-room-onHold', 'Resume the room before transferring it');
}
await transfer(room, guest, transferData); Defensive patterns
Strategy: validation
Validate before calling
import { LivechatRooms } from '@rocket.chat/models';
const room = await LivechatRooms.findOneById(rid, { projection: { onHold: 1 } });
if (room?.onHold) {
throw new Meteor.Error('error-room-onHold', 'Resume the room before transferring');
}
await transfer(room, guest, transferData); Type guard
function isTransferableRoom(v: { onHold?: boolean } | null | undefined): boolean {
return !!v && v.onHold !== true;
} Try / catch
try {
await transfer(room, guest, transferData);
} catch (err) {
if (err instanceof Error && err.message === 'error-room-onHold') {
// offer resume-then-transfer or close instead of a raw error
} else {
throw err;
}
} Prevention
- Check room.onHold in transfer UIs and offer resume/close alternatives
- Make automation (business hours, auto-transfer rules) skip on-hold rooms
- Close stale on-hold conversations on a schedule instead of transferring them
When it happens
Trigger: Calling the room transfer flow (agent 'transfer'/'return' action, or the corresponding livechat transfer method/API) for a room currently on hold — typically a chat put on hold by the visitor not responding (on-hold after inactivity) that an agent then tries to transfer to another department or agent.
Common situations: Agents trying to move stale on-hold chats to another queue, automated transfer rules (e.g. business-hours based) firing against held rooms, or on-hold rooms left in the list that operators attempt to reassign.
Related errors
- error-room-already-closed
- error-room-is-already-on-hold
- error-unserved-rooms-cannot-be-placed-onhold
- This_conversation_is_already_closed
- This_conversation_is_already_closed
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/4d5ff0436477cf41.
Report an issue: GitHub.