RocketChat/Rocket.Chat · error · Error

Invalid message id

Error message

Invalid message id

What it means

Thrown by AppModerationBridge.report when the messageId argument is falsy. The bridge is the Apps Engine entry point for message reporting; it forwards to server/lib/moderation/reportMessage, which itself re-validates and will throw 'error-invalid-message_id' if the id does not correspond to an existing message. This particular throw is the cheap pre-check that prevents a needless DB round-trip.

Source

Thrown at apps/meteor/app/apps/server/bridges/moderation.ts:18

import type { IAppServerOrchestrator } from '@rocket.chat/apps';
import { ModerationBridge } from '@rocket.chat/apps/dist/server/bridges/ModerationBridge';
import type { IMessage } from '@rocket.chat/apps-engine/definition/messages';
import type { IUser } from '@rocket.chat/apps-engine/definition/users';
import { ModerationReports } from '@rocket.chat/models';

import { reportMessage } from '../../../../server/lib/moderation/reportMessage';

export class AppModerationBridge extends ModerationBridge {
	constructor(private readonly orch: IAppServerOrchestrator) {
		super();
	}

	protected async report(messageId: IMessage['id'], description: string, userId: string, appId: string): Promise<void> {
		this.orch.debugLog(`The App ${appId} is creating a new report.`);

		if (!messageId) {
			throw new Error('Invalid message id');
		}

		if (!description) {
			throw new Error('Invalid description');
		}

		await reportMessage(messageId, description, userId || 'rocket.cat');
	}

	protected async dismissReportsByMessageId(messageId: IMessage['id'], reason: string, action: string, appId: string): Promise<void> {
		this.orch.debugLog(`The App ${appId} is dismissing reports by message id.`);

		if (!messageId) {
			throw new Error('Invalid message id');
		}

		await ModerationReports.hideMessageReportsByMessageId(messageId, appId, reason, action);
	}

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Ensure messageId is the persisted message's id — await the send and use the returned id before reporting.
  2. Guard the call: `if (!messageId) return;` or surface a user-facing error instead of letting the bridge throw.
  3. When handling events, read message.id defensively and skip when absent.

Example fix

// before
await moderation.report('', 'spam', userId, appId);

// after
if (message.id) {
  await moderation.report(message.id, 'spam', userId, appId);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!messageId || typeof messageId !== 'string') {
  throw new Error('A valid message id is required to report');
}
await moderation.report(messageId, description, userId, appId);

Type guard

function isMessageId(value: unknown): value is string {
  return typeof value === 'string' && value.length > 0;
}

Prevention

When it happens

Trigger: An App calls the moderation accessor's report endpoint with messageId set to undefined, null, '' or 0 — typically because the App held a message object whose id field was never populated (e.g. a message constructed locally but not yet persisted) or a value extracted from an event payload that was missing the id property.

Common situations: Calling report on a message stub built for sending before the send completed; deserializing a cached message that lost its id; chaining report after a send whose return value (the id) was not awaited; UI actions that pass an empty selection.

Related errors


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