{"record":{"id":"9570f7728b33963f","repo":"RocketChat/Rocket.Chat","slug":"invalid-parameter","errorCode":null,"errorMessage":"invalid-parameter","messagePattern":"invalid-parameter","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/client/lib/getPermaLink.ts","lineNumber":17,"sourceCode":"import type { IMessage, Serialized } from '@rocket.chat/core-typings';\n\nimport { getUserId } from './user';\n\nconst getMessage = async (msgId: string): Promise<Serialized<IMessage> | null> => {\n\ttry {\n\t\tconst { sdk } = await import('../../app/utils/client/lib/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","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/client/lib/getPermaLink.ts#L1-L35","documentation":"Thrown by getPermaLink when the msgId argument is falsy (empty string, null, undefined, 0, false). getPermaLink constructs a permanent URL to a specific message and requires a non-empty message ID. This is a parameter validation guard at the top of the function, before any store or API access.","triggerScenarios":"Code calls getPermaLink('') or getPermaLink(undefined). A UI component passes an uninitialised message ID state variable. A message object without an _id is used to extract the ID. Dynamic import path resolves to a message whose ID extraction yields an empty string.","commonSituations":"Context menu 'copy link' triggered before message data is fully loaded. Message reference from a deleted or pending message that has no _id yet. Programmatic call with a variable that hasn't been assigned. Race condition where the ID is read before the message object is set.","solutions":["Validate msgId is a non-empty string before calling getPermaLink.","Ensure the message object is fully loaded (has _id) before extracting and passing the ID.","Disable or hide the 'copy permalink' UI control until the message ID is available.","Use optional chaining or early-return in the caller if msgId may be absent."],"exampleFix":"// before\nconst link = await getPermaLink(msg?._id);\n// after\nif (!msg?._id) return;\nconst link = await getPermaLink(msg._id);","handlingStrategy":"validation","validationCode":"if (!msgId || typeof msgId !== 'string' || msgId.trim() === '') {\n  throw new Error('msgId is required');\n}\nconst link = await getPermaLink(msgId);","typeGuard":"const isValidMsgId = (id: unknown): id is string =>\n  typeof id === 'string' && id.length > 0;","tryCatchPattern":"try {\n  const link = await getPermaLink(msgId);\n} catch (e) {\n  if (e instanceof Error && e.message === 'invalid-parameter') {\n    // msgId was empty or missing\n    return;\n  }\n  throw e;\n}","preventionTips":["Always validate that msgId is a non-empty string before calling getPermaLink.","Disable permalink-generating UI controls until the message _id is available.","Use optional chaining and early returns in callers when the ID source may be undefined."],"tags":["validation","permalink","parameter-check","message-id"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}