RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-message

error-invalid-message

Error message

Invalid message

What it means

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.

Source

Thrown at apps/meteor/server/meteor-methods/messages/getThreadMessages.ts:38

Meteor.methods<ServerMethods>({
	async getThreadMessages({ tmid, limit, skip }) {
		methodDeprecationLogger.method('getThreadMessages', '9.0.0', '/v1/chat.getThreadMessages');

		if ((limit ?? 0) > MAX_LIMIT) {
			throw new Meteor.Error('error-not-allowed', `max limit: ${MAX_LIMIT}`, {
				method: 'getThreadMessages',
			});
		}

		if (!Meteor.userId() || !settings.get('Threads_enabled')) {
			throw new Meteor.Error('error-not-allowed', 'Threads Disabled', {
				method: 'getThreadMessages',
			});
		}

		if (typeof tmid !== 'string') {
			throw new Meteor.Error('error-invalid-message', 'Invalid message', { method: 'getThreadMessages' });
		}

		const thread = await Messages.findOneById(tmid);
		if (!thread) {
			return [];
		}

		const user = await Meteor.userAsync();
		const room = await Rooms.findOneById(thread.rid);

		if (!user || !room || !(await canAccessRoomAsync(room, user))) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getThreadMessages' });
		}

		if (!thread.tcount) {
			return [];
		}

View on GitHub (pinned to 2a7de45707)

Solutions

  1. 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.
  2. Guard the call site: only invoke the method when typeof tmid === 'string' && tmid.length > 0.
  3. Migrate to the REST endpoint GET /v1/chat.getThreadMessages — the DDP method is deprecated for removal in 9.0.0.
  4. If tmid originates from a URL/query parameter, validate and coerce it to a string before it reaches the method call.

Example fix

// before — `message` is the thread ROOT: it has no tmid of its own,
// so tmid is undefined and the server throws error-invalid-message
await Meteor.callAsync('getThreadMessages', { tmid: message.tmid });

// after — root: use its _id; reply: use its tmid (which points at the root)
await Meteor.callAsync('getThreadMessages', { tmid: message.tmid ?? message._id });
Defensive patterns

Strategy: validation

Validate before calling

const isThreadParentId = (value: unknown): value is IMessage['_id'] =>
  typeof value === 'string' && value.length > 0;

if (!isThreadParentId(tmid)) {
  throw new TypeError('tmid must be the thread-parent message _id (non-empty string)');
}
const messages = await Meteor.callAsync('getThreadMessages', { tmid, limit: 50 });

Type guard

const isThreadParentId = (value: unknown): value is IMessage['_id'] =>
  typeof value === 'string' && value.length > 0;

Try / catch

try {
  const messages = await Meteor.callAsync('getThreadMessages', { tmid });
} catch (error) {
  if (error instanceof Meteor.Error && error.error === 'error-invalid-message') {
    // deterministic bad input — log loudly and fix the caller; do NOT retry.
    // note: a valid but unknown tmid returns [] instead of throwing
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@2a7de45707 (2026-08-21). Data as JSON: /api/errors/247678ed70832202. Report an issue: GitHub.