RocketChat/Rocket.Chat · error · Error
room-still-open
Error message
room-still-open
What it means
Thrown by requestPdfTranscript in requestPdfTranscript.ts:17 when the supplied room is still open (room.open truthy). PDF transcripts are only generated for closed omnichannel rooms; requesting one on an open room is a contract violation. NOTE: plain `new Error('room-still-open')`.
Source
Thrown at apps/meteor/ee/server/lib/omnichannel/requestPdfTranscript.ts:17
import { OmnichannelTranscript, QueueWorker } from '@rocket.chat/core-services';
import type { AtLeast, IOmnichannelRoom } from '@rocket.chat/core-typings';
import ExpiryMap from 'expiry-map';
import { logger } from './logger';
// Allow to request a transcript again after 15 seconds, assuming the first one didn't complete
// This won't prevent multiple transcript generated for the same room in a multi-instance deployment since state is not shared, but we're ok with the drawbacks
const LockMap = new ExpiryMap<string, boolean>(15000);
const serviceName = 'omnichannel-transcript' as const;
export const requestPdfTranscript = async (
room: AtLeast<IOmnichannelRoom, '_id' | 'open' | 'v' | 'pdfTranscriptFileId'>,
requestedBy: string,
): Promise<void> => {
if (room.open) {
throw new Error('room-still-open');
}
if (!room.v) {
throw new Error('improper-room-state');
}
// Don't request a transcript if there's already one requested
if (LockMap.has(room._id) || room.pdfTranscriptFileId) {
// TODO: use logger
logger.info({ msg: `Transcript already requested`, roomId: room._id });
return;
}
LockMap.set(room._id, true);
const details = { details: { rid: room._id, userId: requestedBy, from: serviceName } };
// Make the whole process sync when running on test mode
// This will prevent the usage of timeouts on the tests of this functionality :)View on GitHub (pinned to f9d3ec372b)
Solutions
- Ensure the room is closed (room.open === false) before invoking requestPdfTranscript.
- If close is async, wait for the close callback / room-updated subscription before requesting.
- Match by message 'room-still-open' and retry once the close settles.
Example fix
// before
await requestPdfTranscript(room, requestedBy);
// after
if (room.open) {
await closeRoom(room._id); // ensure settled
room = await Rooms.findOneById(room._id);
}
await requestPdfTranscript(room, requestedBy); Defensive patterns
Strategy: validation
Validate before calling
if (room.open) {
// ensure the close has settled
await closeRoom(room._id);
room = await Rooms.findOneById(room._id);
} Type guard
const isRoomClosed = (room: { open?: boolean }) => room.open === false; Try / catch
try { await requestPdfTranscript(room, requestedBy); }
catch (e) {
if (e instanceof Error && e.message === 'room-still-open') { /* close first */ return; }
throw e;
} Prevention
- Wait for the close callback before requesting a transcript.
- Disable the transcript action while room.open is true.
When it happens
Trigger: Calling requestPdfTranscript before the chat was closed (CloseRoom), or right after a close that has not yet flushed to the room record being passed in. Passing a freshly-constructed room object whose open field defaults true.
Common situations: Race between client closing the room and triggering transcript; webhook fired on a 'transcript requested' event for a still-open room; UI lets the user click 'get transcript' too early.
Related errors
- improper-room-state
- error-invalid-room
- error-duplicated-sla
- SLA with id ${_id} not found
- error-business-hour-name-already-in-use
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/faaa4d9b5ec2434c.
Report an issue: GitHub.