RocketChat/Rocket.Chat · error · Error
improper-room-state
Error message
improper-room-state
What it means
Thrown by requestPdfTranscript in requestPdfTranscript.ts:21 when room.v (the omnichannel visitor reference) is missing. A valid omnichannel room must reference its visitor; absence means the object passed in is malformed or the room is not actually an omnichannel conversation. NOTE: plain `new Error('improper-room-state')`.
Source
Thrown at apps/meteor/ee/server/lib/omnichannel/requestPdfTranscript.ts:21
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 :)
if (process.env.TEST_MODE) {
await OmnichannelTranscript.workOnPdf(details);
return;
}View on GitHub (pinned to f9d3ec372b)
Solutions
- Fetch the room with at least { _id, open, v, pdfTranscriptFileId } projected, or full document.
- Type-narrow the caller to AtLeast<IOmnichannelRoom,'_id'|'open'|'v'|'pdfTranscriptFileId'> before calling.
- Match by message 'improper-room-state' and surface a data-integrity error.
Example fix
// before
const room = await Rooms.findOneById(rid, { projection: { _id: 1, open: 1 } });
await requestPdfTranscript(room, requestedBy);
// after
const room = await Rooms.findOneById(rid, {
projection: { _id: 1, open: 1, v: 1, pdfTranscriptFileId: 1 },
});
await requestPdfTranscript(room, requestedBy); Defensive patterns
Strategy: type-guard
Validate before calling
const room = await Rooms.findOneById(rid, {
projection: { _id: 1, open: 1, v: 1, pdfTranscriptFileId: 1 },
}); Type guard
const hasVisitor = (room: unknown): room is { v: unknown } =>
typeof room === 'object' && room !== null && 'v' in room && (room as any).v != null; Try / catch
try { await requestPdfTranscript(room, requestedBy); }
catch (e) {
if (e instanceof Error && e.message === 'improper-room-state') { /* refetch with v */ return; }
throw e;
} Prevention
- Project v when fetching rooms for transcript purposes.
- Type the caller as AtLeast<IOmnichannelRoom,'_id'|'open'|'v'|'pdfTranscriptFileId'>.
When it happens
Trigger: Calling requestPdfTranscript with a room that has no v field (e.g. a typed-but-incomplete object built by hand, or a non-omnichannel room leaked into the call path).
Common situations: Caller fetches the room with a projection that excludes v; service-to-service call passes a Pick<...> that omits v; degraded data after a migration that dropped visitor refs.
Related errors
- room-still-open
- error-invalid-room
- SLA not found with id: ${slaId}
- error-duplicated-sla
- SLA with id ${_id} not found
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/da50c886824b43d8.
Report an issue: GitHub.