{"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":293,"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":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/e4b8178b205510181a96ceefee043d0abcd13e5a/apps/meteor/server/api/v1/chat.ts#L275-L311","documentation":"Thrown by POST chat.readThread (apps/meteor/server/api/v1/chat.ts:289) when Messages.findOneById(tmid) returns null or a document without an rid. The endpoint needs the thread's parent message to resolve which room the thread lives in, so an unknown or malformed tmid fails here with error-invalid-message. Note the schema only requires tmid to be a non-empty string, so any well-formed but wrong id reaches this check.","triggerScenarios":"POST /api/v1/chat.readThread with a tmid that (a) does not exist in the Messages collection (typo, stale id from another workspace, deleted thread parent), (b) belongs to a message whose rid field is missing, or (c) was confused with a thread reply id or a room id. Threads must be enabled or you get error 320 first.","commonSituations":"Bots persisting message ids across workspace re-imports/migrations where ids change; clients resending tmid after the parent message was purged by a retention policy; passing msgId of a reply instead of the thread's main message; copy-paste truncation of the 17-char Mongo id.","solutions":["Verify the id exists and is a thread parent first: GET /api/v1/chat.getMessage?msgId=<tmid> must return a message (and its rid) before you mark the thread read.","Confirm you are passing the thread's main message id (the message other replies reference via tmid), not a reply's _id and not a room id.","If the parent was deleted, drop the unread state locally - there is nothing to mark read.","Add a 404-style branch in your client for error-invalid-message so stale ids are discarded instead of retried."],"exampleFix":"// before\nawait rc.post('/api/v1/chat.readThread', { tmid: cachedTmid });\n// error-invalid-message when the parent message is gone\n\n// after\ntry {\n  await rc.get('/api/v1/chat.getMessage', { params: { msgId: cachedTmid } });\n  await rc.post('/api/v1/chat.readThread', { tmid: cachedTmid });\n} catch (e) {\n  if (e?.errorType === 'error-invalid-message') forgetThread(cachedTmid);\n}","handlingStrategy":"validation","validationCode":"const res = await rc.get('/api/v1/chat.getMessage', { params: { msgId: tmid } });\nif (!res?.message?.rid) throw new Error('tmid is not a valid thread parent');\nawait rc.post('/api/v1/chat.readThread', { tmid });","typeGuard":"const isValidThreadParent = (m: { rid?: string } | null | undefined): m is { rid: string } =>\n  typeof m?.rid === 'string' && m.rid.length > 0;","tryCatchPattern":"try {\n  await rc.post('/api/v1/chat.readThread', { tmid });\n} catch (e) {\n  if (e?.errorType === 'error-invalid-message') discardThread(tmid); // gone, stop syncing\n  else throw e;\n}","preventionTips":["Use ids only from live API responses in the same workspace; never persist them across re-imports.","Pass the thread's parent message id (what replies point at via tmid), not a reply id or room id."],"tags":["rocket-chat","rest-api","threads","invalid-id","chat-readthread","message-not-found"],"backgroundTag":"message-not-found","analyzedSha":"e4b8178b205510181a96ceefee043d0abcd13e5a","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}