{"record":{"id":"247678ed70832202","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-message-247678","errorCode":"error-invalid-message","errorMessage":"Invalid message","messagePattern":"Invalid message","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/messages/getThreadMessages.ts","lineNumber":38,"sourceCode":"\nMeteor.methods<ServerMethods>({\n\tasync getThreadMessages({ tmid, limit, skip }) {\n\t\tmethodDeprecationLogger.method('getThreadMessages', '9.0.0', '/v1/chat.getThreadMessages');\n\n\t\tif ((limit ?? 0) > MAX_LIMIT) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', `max limit: ${MAX_LIMIT}`, {\n\t\t\t\tmethod: 'getThreadMessages',\n\t\t\t});\n\t\t}\n\n\t\tif (!Meteor.userId() || !settings.get('Threads_enabled')) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Threads Disabled', {\n\t\t\t\tmethod: 'getThreadMessages',\n\t\t\t});\n\t\t}\n\n\t\tif (typeof tmid !== 'string') {\n\t\t\tthrow new Meteor.Error('error-invalid-message', 'Invalid message', { method: 'getThreadMessages' });\n\t\t}\n\n\t\tconst thread = await Messages.findOneById(tmid);\n\t\tif (!thread) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst user = await Meteor.userAsync();\n\t\tconst room = await Rooms.findOneById(thread.rid);\n\n\t\tif (!user || !room || !(await canAccessRoomAsync(room, user))) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getThreadMessages' });\n\t\t}\n\n\t\tif (!thread.tcount) {\n\t\t\treturn [];\n\t\t}\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2a7de457074cbb4d4373fbd9a4e5bea292c9c764/apps/meteor/server/meteor-methods/messages/getThreadMessages.ts#L20-L56","documentation":"getThreadMessages (deprecated in 9.0.0 in favor of GET /v1/chat.getThreadMessages) throws error-invalid-message when its tmid parameter is not a string — a manual typeof guard that runs before any database access. It does NOT mean 'thread not found': a well-typed but unknown tmid returns an empty array (getThreadMessages.ts:41-44 returns [] when the thread message is missing). Hitting it means the client sent undefined, null, a number, or an object where the thread-parent message _id string was expected.","triggerScenarios":"Meteor.call('getThreadMessages', { tmid }) where tmid is undefined (e.g., reading .tmid off a thread ROOT message, which has no tmid of its own — only replies carry tmid), null, a numeric id, or an entire message object instead of message._id/message.tmid. The check runs after the limit check (limit > 100 throws first) and the Threads_enabled/auth check, so it only fires for logged-in users on a threads-enabled workspace with a valid limit.","commonSituations":"Client refactors renaming fields (tmid vs _id), optional-chaining bugs like message?.tmid on the root message itself, or passing a deserialized document where its id was expected. Also hit by integrations assuming meteor/check-style argument validation — this method validates with manual typeof checks, so malformed input reaches the method instead of being rejected at the boundary.","solutions":["Pass the thread-parent message's _id as a non-empty string: for a reply message use its tmid field (it points at the root); for the root message use its _id.","Guard the call site: only invoke the method when typeof tmid === 'string' && tmid.length > 0.","Migrate to the REST endpoint GET /v1/chat.getThreadMessages — the DDP method is deprecated for removal in 9.0.0.","If tmid originates from a URL/query parameter, validate and coerce it to a string before it reaches the method call."],"exampleFix":"// before — `message` is the thread ROOT: it has no tmid of its own,\n// so tmid is undefined and the server throws error-invalid-message\nawait Meteor.callAsync('getThreadMessages', { tmid: message.tmid });\n\n// after — root: use its _id; reply: use its tmid (which points at the root)\nawait Meteor.callAsync('getThreadMessages', { tmid: message.tmid ?? message._id });","handlingStrategy":"validation","validationCode":"const isThreadParentId = (value: unknown): value is IMessage['_id'] =>\n  typeof value === 'string' && value.length > 0;\n\nif (!isThreadParentId(tmid)) {\n  throw new TypeError('tmid must be the thread-parent message _id (non-empty string)');\n}\nconst messages = await Meteor.callAsync('getThreadMessages', { tmid, limit: 50 });","typeGuard":"const isThreadParentId = (value: unknown): value is IMessage['_id'] =>\n  typeof value === 'string' && value.length > 0;","tryCatchPattern":"try {\n  const messages = await Meteor.callAsync('getThreadMessages', { tmid });\n} catch (error) {\n  if (error instanceof Meteor.Error && error.error === 'error-invalid-message') {\n    // deterministic bad input — log loudly and fix the caller; do NOT retry.\n    // note: a valid but unknown tmid returns [] instead of throwing\n  }\n  throw error;\n}","preventionTips":["Derive tmid from a reply's tmid field or the root's _id — thread ROOT messages have no tmid of their own.","Validate the whole params object before any DDP call; these methods use manual typeof checks, not meteor/check boundary validation.","Never retry this error — it is deterministic bad input, not a transient failure.","Reserve runtime error handling for error-not-allowed (access/threads-disabled); type errors should be impossible by construction."],"tags":["meteor-methods","ddp","threads","argument-validation","type-error"],"backgroundTag":"meteor-invalid-method-argument","analyzedSha":"2a7de457074cbb4d4373fbd9a4e5bea292c9c764","analyzedAt":"2026-08-21T15:01:34.830Z","contentChangedAt":"2026-08-21T15:01:34.830Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}