{"record":{"id":"0c21c63e5b34771a","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-message-0c21c6","errorCode":"error-invalid-message","errorMessage":"Invalid Message","messagePattern":"Invalid Message","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/chat.ts","lineNumber":289,"sourceCode":"\t\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\t\tenum: [true],\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['success'],\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tasync function action() {\n\t\t\tif (!settings.get<boolean>('Threads_enabled')) {\n\t\t\t\tthrow new Meteor.Error('error-not-allowed', 'Threads Disabled');\n\t\t\t}\n\n\t\t\tconst { tmid } = this.bodyParams;\n\n\t\t\tconst thread = await Messages.findOneById(tmid, { projection: { rid: 1 } });\n\t\t\tif (!thread?.rid) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-message', 'Invalid Message');\n\t\t\t}\n\n\t\t\tconst [user, room] = await Promise.all([\n\t\t\t\tUsers.findOneById(this.userId),\n\t\t\t\tRooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } }),\n\t\t\t]);\n\n\t\t\tif (!room) {\n\t\t\t\tthrow new Meteor.Error('error-room-does-not-exist', 'This room does not exist');\n\t\t\t}\n\n\t\t\tif (!user || !(await canAccessRoomAsync(room, user))) {\n\t\t\t\tthrow new Meteor.Error('error-not-allowed', 'Not Allowed');\n\t\t\t}\n\n\t\t\tawait callbacks.run('beforeReadMessages', room._id, user._id);\n\t\t\tawait readThread({ user, room, tmid });\n","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/chat.ts#L271-L307","documentation":"Thrown by chat.readThread when Messages.findOneById(tmid, { projection: { rid: 1 } }) returns no document or one without an rid - i.e., the supplied thread parent message (tmid) does not exist. Meteor.Error code 'error-invalid-message'. This runs only after the Threads_enabled check passes.","triggerScenarios":"POST chat.readThread with a tmid that is not a real message id, or that is a message with no rid (malformed/deleted).","commonSituations":"Stale tmid from a thread whose parent message was deleted; tmid typo; using a reply message id as tmid instead of the thread root.","solutions":["Confirm tmid refers to the thread-root message (the parent), not a reply within the thread.","Validate that the thread root still exists before marking read.","Handle error-invalid-message by refreshing the thread list."],"exampleFix":"// before\nawait fetch('/api/v1/chat.readThread', { method: 'POST', body: JSON.stringify({ tmid }) });\n\n// after\nconst root = await fetchMessage(tmid);\nif (!root || !root.rid) {\n  refreshThreadList();\n  return;\n}\nawait fetch('/api/v1/chat.readThread', { method: 'POST', body: JSON.stringify({ tmid }) });","handlingStrategy":"validation","validationCode":"async function threadRootExists(tmid) {\n  const msg = await Messages.findOneById(tmid, { projection: { rid: 1 } });\n  return Boolean(msg && msg.rid);\n}","typeGuard":"function isValidThreadRoot(msg) {\n  return Boolean(msg) && typeof msg.rid === 'string' && msg.rid.length > 0;\n}","tryCatchPattern":"try {\n  await api.readThread({ tmid });\n} catch (e) {\n  if (e.error === 'error-invalid-message') {\n    refreshThreadList(); // tmid stale or wrong\n    return;\n  }\n  throw e;\n}","preventionTips":["Pass the thread-root message id as tmid, not a reply id.","Validate the thread root still exists before marking read.","Refresh thread lists when tmid lookups fail."],"tags":["chat","threads","invalid-message","validation"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}