{"record":{"id":"da50c886824b43d8","repo":"RocketChat/Rocket.Chat","slug":"improper-room-state","errorCode":null,"errorMessage":"improper-room-state","messagePattern":"improper-room-state","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/omnichannel/requestPdfTranscript.ts","lineNumber":21,"sourceCode":"import ExpiryMap from 'expiry-map';\n\nimport { logger } from './logger';\n\n// Allow to request a transcript again after 15 seconds, assuming the first one didn't complete\n// 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\nconst LockMap = new ExpiryMap<string, boolean>(15000);\n\nconst serviceName = 'omnichannel-transcript' as const;\nexport const requestPdfTranscript = async (\n\troom: AtLeast<IOmnichannelRoom, '_id' | 'open' | 'v' | 'pdfTranscriptFileId'>,\n\trequestedBy: string,\n): Promise<void> => {\n\tif (room.open) {\n\t\tthrow new Error('room-still-open');\n\t}\n\n\tif (!room.v) {\n\t\tthrow new Error('improper-room-state');\n\t}\n\n\t// Don't request a transcript if there's already one requested\n\tif (LockMap.has(room._id) || room.pdfTranscriptFileId) {\n\t\t// TODO: use logger\n\t\tlogger.info({ msg: `Transcript already requested`, roomId: room._id });\n\t\treturn;\n\t}\n\n\tLockMap.set(room._id, true);\n\n\tconst details = { details: { rid: room._id, userId: requestedBy, from: serviceName } };\n\t// Make the whole process sync when running on test mode\n\t// This will prevent the usage of timeouts on the tests of this functionality :)\n\tif (process.env.TEST_MODE) {\n\t\tawait OmnichannelTranscript.workOnPdf(details);\n\t\treturn;\n\t}","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/lib/omnichannel/requestPdfTranscript.ts#L3-L39","documentation":"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')`.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst room = await Rooms.findOneById(rid, { projection: { _id: 1, open: 1 } });\nawait requestPdfTranscript(room, requestedBy);\n\n// after\nconst room = await Rooms.findOneById(rid, {\n  projection: { _id: 1, open: 1, v: 1, pdfTranscriptFileId: 1 },\n});\nawait requestPdfTranscript(room, requestedBy);","handlingStrategy":"type-guard","validationCode":"const room = await Rooms.findOneById(rid, {\n  projection: { _id: 1, open: 1, v: 1, pdfTranscriptFileId: 1 },\n});","typeGuard":"const hasVisitor = (room: unknown): room is { v: unknown } =>\n  typeof room === 'object' && room !== null && 'v' in room && (room as any).v != null;","tryCatchPattern":"try { await requestPdfTranscript(room, requestedBy); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'improper-room-state') { /* refetch with v */ return; }\n  throw e;\n}","preventionTips":["Project v when fetching rooms for transcript purposes.","Type the caller as AtLeast<IOmnichannelRoom,'_id'|'open'|'v'|'pdfTranscriptFileId'>."],"tags":["omnichannel","pdf-transcript","room-state","data-integrity","plain-error"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}