{"record":{"id":"bbb57d16efc9f931","repo":"RocketChat/Rocket.Chat","slug":"room-not-found-bbb57d","errorCode":null,"errorMessage":"room-not-found","messagePattern":"room-not-found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/client/lib/getPermaLink.ts","lineNumber":29,"sourceCode":"\t\treturn null;\n\t}\n};\n\nexport const getPermaLink = async (msgId: string): Promise<string> => {\n\tif (!msgId) {\n\t\tthrow new Error('invalid-parameter');\n\t}\n\n\tconst { Messages, Rooms, Subscriptions } = await import('../stores');\n\n\tconst msg = Messages.state.get(msgId) || (await getMessage(msgId));\n\tif (!msg) {\n\t\tthrow new Error('message-not-found');\n\t}\n\tconst roomData = Rooms.state.get(msg.rid);\n\n\tif (!roomData) {\n\t\tthrow new Error('room-not-found');\n\t}\n\n\tconst subData = Subscriptions.state.find((record) => record.rid === roomData._id && record.u._id === getUserId());\n\n\tconst { roomCoordinator } = await import('./rooms/roomCoordinator');\n\n\tconst roomURL = roomCoordinator.getURL(roomData.t, { ...(subData || roomData), tab: '' });\n\treturn `${roomURL}?msg=${msgId}`;\n};\n","sourceCodeStart":11,"sourceCodeEnd":39,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2f18297792c973da326be7253f497d239ea6f2b0/apps/meteor/client/lib/getPermaLink.ts#L11-L39","documentation":"After resolving the message, getPermaLink() throws 'room-not-found' when Rooms.state.get(msg.rid) returns undefined — the room containing the message is not present in the local Rooms store. Note the asymmetry: the message has a server fallback, the room lookup does not.","triggerScenarios":"Building a permalink for a message fetched from the server while its room was never loaded into Rooms.state (cross-room permalink, notification deep-link, search result from an unopened room), or when rooms have not finished syncing.","commonSituations":"Permalinks generated from search or notification contexts where only the message was fetched; startup races before the rooms subscription completes; room deleted server-side while the message row persists.","solutions":["Ensure the room is loaded (open it, or fetch via /v1/rooms.info and hydrate the store) before generating the permalink.","Handle the throw with a graceful fallback (e.g. link to the room once it loads).","Await initial rooms sync before enabling copy-link in secondary surfaces."],"exampleFix":"// before\nconst url = await getPermaLink(msgId); // throws room-not-found\n\n// after\nconst msg = Messages.state.get(msgId) ?? (await sdk.rest.get('/v1/chat.getMessage', { msgId })).message;\nif (!Rooms.state.get(msg.rid)) {\n  await sdk.rest.get('/v1/rooms.info', { roomId: msg.rid }); // hydrate store first\n}\nconst url = await getPermaLink(msgId);","handlingStrategy":"validation","validationCode":"const msg = Messages.state.get(msgId) ?? (await sdk.rest.get('/v1/chat.getMessage', { msgId })).message;\nif (!Rooms.state.get(msg.rid)) {\n  await sdk.rest.get('/v1/rooms.info', { roomId: msg.rid }); // hydrate the store first\n}\nconst url = await getPermaLink(msgId);","typeGuard":null,"tryCatchPattern":"try {\n  const url = await getPermaLink(msgId);\n} catch (e) {\n  if (e instanceof Error && e.message === 'room-not-found') {\n    // room not cached: hydrate and retry once\n    await sdk.rest.get('/v1/rooms.info', { roomId });\n    return await getPermaLink(msgId);\n  }\n  throw e;\n}","preventionTips":["Hydrate Rooms.state for cross-room messages before building permalinks.","Remember the room lookup has no server fallback, unlike the message lookup.","In secondary surfaces (search, notifications), fetch room info alongside the message."],"tags":["permalink","rooms","cache"],"backgroundTag":"room-not-found","analyzedSha":"2f18297792c973da326be7253f497d239ea6f2b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}