RocketChat/Rocket.Chat · error · Meteor.Error

error-message-change-to-thread

error-message-change-to-thread

Error message

Cannot update message to a thread

What it means

Thrown by Rocket.Chat's updateMessage when the stored message has no tmid (a top-level channel message) but the edit payload includes one. Messages cannot be moved into a thread by editing; thread membership is fixed at creation time. The server enforces this by comparing originalMessage.tmid against the incoming message.tmid.

Source

Thrown at apps/meteor/server/meteor-methods/messages/updateMessage.ts:49

		}
	});

	// IF the message has custom fields, always update
	// Ideally, we'll compare the custom fields to check for change, but since we don't know the shape of
	// custom fields, as it's user defined, we're gonna update
	const msgText = originalMessage?.attachments?.[0]?.description ?? originalMessage.msg;
	if (msgText === message.msg && !previewUrls && !message.customFields) {
		return;
	}

	if (!!message.tmid && originalMessage._id === message.tmid) {
		throw new Meteor.Error('error-message-same-as-tmid', 'Cannot set tmid the same as the _id', {
			method: 'updateMessage',
		});
	}

	if (!originalMessage.tmid && !!message.tmid) {
		throw new Meteor.Error('error-message-change-to-thread', 'Cannot update message to a thread', { method: 'updateMessage' });
	}

	const _hasPermission = await hasPermissionAsync(uid, 'edit-message', message.rid);
	const editAllowed = settings.get('Message_AllowEditing');
	const editOwn = originalMessage.u && originalMessage.u._id === uid;

	if (!_hasPermission && (!editAllowed || !editOwn)) {
		throw new Meteor.Error('error-action-not-allowed', 'Message editing not allowed', {
			method: 'updateMessage',
			action: 'Message_editing',
		});
	}

	const blockEditInMinutes = settings.get('Message_AllowEditing_BlockEditInMinutes');
	const bypassBlockTimeLimit = await hasPermissionAsync(uid, 'bypass-time-limit-edit-and-delete', message.rid);

	if (!bypassBlockTimeLimit && Match.test(blockEditInMinutes, Number) && blockEditInMinutes !== 0) {
		let currentTsDiff = 0;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Strip tmid from the edit payload; send only the fields being changed
  2. To get the content into a thread: send a new message with tmid of the desired parent, then delete the original
  3. Guard payload construction: include tmid only when editing a message that already has one

Example fix

// before
Meteor.call('updateMessage', { _id: msg._id, msg: text, tmid: parent._id }); // msg is not threaded

// after
Meteor.call('updateMessage', { _id: msg._id, msg: text });
// to thread it instead: post a new reply
// Meteor.call('sendMessage', { rid, msg: text, tmid: parent._id })
Defensive patterns

Strategy: validation

Validate before calling

const mayIncludeTmid = (original: { tmid?: string }, payload: { tmid?: string }): boolean =>
  !payload.tmid || !!original.tmid;
if (!mayIncludeTmid(originalMessage, payload)) {
  delete payload.tmid; // a non-thread message cannot be edited into a thread
}

Type guard

const isChangeToThreadError = (e: unknown): e is Meteor.Error =>
  typeof e === 'object' && e !== null && (e as { error?: string }).error === 'error-message-change-to-thread';

Try / catch

try {
  await Meteor.callAsync('updateMessage', payload);
} catch (e) {
  if (isChangeToThreadError(e)) {
    const { tmid, ...rest } = payload;
    return Meteor.callAsync('updateMessage', rest);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling Meteor.call('updateMessage', { ...msg, tmid }) where the stored message has no tmid — e.g. an edit form pre-filled with thread context, or payload builders that unconditionally attach tmid to every edit call.

Common situations: An 'edit in thread' UI applied to a message originally posted directly to the channel; bots reusing thread-reply payload shapes for all edits; attempts to retro-organize old messages into threads via bulk edit.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/bd236c8b391f205c. Report an issue: GitHub.