{"record":{"id":"955add2c3fc3ba9e","repo":"RocketChat/Rocket.Chat","slug":"error-message-same-as-tmid","errorCode":"error-message-same-as-tmid","errorMessage":"Cannot set tmid the same as the _id","messagePattern":"Cannot set tmid the same as the _id","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/messages/updateMessage.ts","lineNumber":43,"sourceCode":"\n\tObject.entries(message).forEach(([key, value]) => {\n\t\tif (!allowedEditedFields.includes(key) && value !== originalMessage[key as keyof IMessage]) {\n\t\t\tthrow new Meteor.Error('error-invalid-update-key', `Cannot update the message ${key}`, {\n\t\t\t\tmethod: 'updateMessage',\n\t\t\t});\n\t\t}\n\t});\n\n\t// IF the message has custom fields, always update\n\t// Ideally, we'll compare the custom fields to check for change, but since we don't know the shape of\n\t// custom fields, as it's user defined, we're gonna update\n\tconst msgText = originalMessage?.attachments?.[0]?.description ?? originalMessage.msg;\n\tif (msgText === message.msg && !previewUrls && !message.customFields) {\n\t\treturn;\n\t}\n\n\tif (!!message.tmid && originalMessage._id === message.tmid) {\n\t\tthrow new Meteor.Error('error-message-same-as-tmid', 'Cannot set tmid the same as the _id', {\n\t\t\tmethod: 'updateMessage',\n\t\t});\n\t}\n\n\tif (!originalMessage.tmid && !!message.tmid) {\n\t\tthrow new Meteor.Error('error-message-change-to-thread', 'Cannot update message to a thread', { method: 'updateMessage' });\n\t}\n\n\tconst _hasPermission = await hasPermissionAsync(uid, 'edit-message', message.rid);\n\tconst editAllowed = settings.get('Message_AllowEditing');\n\tconst editOwn = originalMessage.u && originalMessage.u._id === uid;\n\n\tif (!_hasPermission && (!editAllowed || !editOwn)) {\n\t\tthrow new Meteor.Error('error-action-not-allowed', 'Message editing not allowed', {\n\t\t\tmethod: 'updateMessage',\n\t\t\taction: 'Message_editing',\n\t\t});\n\t}","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/messages/updateMessage.ts#L25-L61","documentation":"Thrown by Rocket.Chat's updateMessage Meteor method when an edit payload sets tmid (the thread parent id) to the message's own _id. The server compares the stored message's _id with the incoming tmid and rejects a self-referencing thread reference, because a message cannot be its own thread parent. This is a request-validation guard, not a server fault.","triggerScenarios":"Calling Meteor.call('updateMessage', message) with message.tmid truthy and equal to the _id of the message being edited. Typically the payload is built by spreading an existing message object and tmid gets assigned from the wrong variable (the message's own _id instead of its parent's _id).","commonSituations":"Custom clients or Apps-Engine code that reuses a fetched message object as the edit payload; thread UIs mixing up the current message id and the parent message id; bots or migration scripts replaying edits with reconstructed payloads.","solutions":["Remove tmid from the edit payload — the server preserves the message's existing threading; only send fields you intend to change (msg, attachments, customFields)","If the goal is to reply inside a thread, send a new message with tmid set to the parent message's _id instead of editing","Audit payload construction so tmid always references the parent message and never the edited message's own _id"],"exampleFix":"// before\nMeteor.call('updateMessage', { ...msg, tmid: msg._id }); // self reference\n\n// after\nconst { tmid, ...changes } = msg;\nMeteor.call('updateMessage', { _id: msg._id, ...changes }); // no tmid on edit","handlingStrategy":"validation","validationCode":"const tmidIsValid = (payload: { _id: string; tmid?: string }): boolean =>\n  !payload.tmid || payload.tmid !== payload._id;\nif (!tmidIsValid(editPayload)) {\n  throw new Error('refusing to submit: tmid equals _id');\n}","typeGuard":"const isSameTmidError = (e: unknown): e is Meteor.Error =>\n  typeof e === 'object' && e !== null && (e as { error?: string }).error === 'error-message-same-as-tmid';","tryCatchPattern":"try {\n  await Meteor.callAsync('updateMessage', payload);\n} catch (e) {\n  if (isSameTmidError(e)) {\n    const { tmid, ...rest } = payload;\n    return Meteor.callAsync('updateMessage', rest); // retry once without tmid\n  }\n  throw e;\n}","preventionTips":["Build edit payloads from explicit fields (_id, msg) instead of spreading fetched message objects","Treat tmid as create-only: never set it on an edit","Add a client-side assertion that tmid !== _id before every updateMessage call"],"tags":["rocket-chat","meteor","message-editing","threads","tmid","validation"],"backgroundTag":"circular-reference-validation","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}