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

  1. Fetch the room with at least { _id, open, v, pdfTranscriptFileId } projected, or full document.
  2. Type-narrow the caller to AtLeast<IOmnichannelRoom,'_id'|'open'|'v'|'pdfTranscriptFileId'> before calling.
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/da50c886824b43d8. Report an issue: GitHub.