{"record":{"id":"8096da246c9dd1e8","repo":"RocketChat/Rocket.Chat","slug":"message-not-found-8096da","errorCode":null,"errorMessage":"message-not-found","messagePattern":"message-not-found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/client/lib/getPermaLink.ts","lineNumber":24,"sourceCode":"\ttry {\n\t\tconst { sdk } = await import('./SDKClient');\n\t\tconst { message } = await sdk.rest.get('/v1/chat.getMessage', { msgId });\n\t\treturn message;\n\t} catch {\n\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":6,"sourceCodeEnd":39,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2f18297792c973da326be7253f497d239ea6f2b0/apps/meteor/client/lib/getPermaLink.ts#L6-L39","documentation":"getPermaLink() throws 'message-not-found' when the message is neither in the local Messages store (Messages.state.get(msgId)) nor fetchable via GET /v1/chat.getMessage (getMessage() swallows errors and returns null). It means the permalink target genuinely cannot be resolved by this client.","triggerScenarios":"Building a permalink for a deleted message, a message in a room the user cannot read, or an id that does not exist on this server; also when the REST call fails (network, permission) — the catch converts that to the same throw.","commonSituations":"Copy-link on a message removed moments earlier; permalinks shared across workspaces; API token without read access to the target room; offline moment during the fallback fetch.","solutions":["Verify the message id exists and the user has read access to its room.","Handle the throw and surface a 'message not available' state instead of a broken link.","If the message should exist, retry once the network/permissions settle."],"exampleFix":"// before\nconst url = await getPermaLink(msgId);\n\n// after\ntry {\n  const url = await getPermaLink(msgId);\n} catch (e) {\n  if (e instanceof Error && e.message === 'message-not-found') {\n    dispatchToastMessage({ type: 'error', message: 'Message no longer available' });\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!Messages.state.get(msgId)) {\n  try {\n    await sdk.rest.get('/v1/chat.getMessage', { msgId });\n  } catch {\n    return; // gone or inaccessible — skip permalink generation\n  }\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 === 'message-not-found') {\n    dispatchToastMessage({ type: 'error', message: 'Message no longer available' });\n    return;\n  }\n  throw e;\n}","preventionTips":["Confirm read access to the message's room before generating permalinks.","Expect permalinks to deleted messages to fail — keep a graceful fallback UI.","Note getMessage() swallows REST errors, so network failures surface as not-found too."],"tags":["permalink","messages","rest-api"],"backgroundTag":"message-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"}