RocketChat/Rocket.Chat · error · Error

invalid user

Error message

invalid user

What it means

`BannerModule` is the UiKit core app `banner-core` that processes close events for in-app banners. `viewClosed()` destructures `payload.user._id`; when it is missing there is no user to dismiss the banner for, so the module throws a plain `Error('invalid user')` before ever reaching `Banner.dismiss(userId, bannerId)`. This is a payload-shape guard, not an authentication check.

Source

Thrown at apps/meteor/server/modules/core-apps/banner.module.ts:16

import { Banner } from '@rocket.chat/core-services';
import type { IUiKitCoreApp, UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';
import type * as UiKit from '@rocket.chat/ui-kit';

export class BannerModule implements IUiKitCoreApp {
	appId = 'banner-core';

	// when banner view is closed we need to dismiss that banner for that user
	async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {
		const {
			payload: { view: { viewId: bannerId } = {} },
			user: { _id: userId } = {},
		} = payload;

		if (!userId) {
			throw new Error('invalid user');
		}

		if (!bannerId) {
			throw new Error('invalid banner');
		}

		if (!payload.triggerId) {
			throw new Error('invalid triggerId');
		}

		await Banner.dismiss(userId, bannerId);

		return {
			type: 'banner.close',
			triggerId: payload.triggerId,
			appId: payload.appId,
			viewId: bannerId,
		};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Include `user: { _id: userId }` in the `viewClosed` payload.
  2. For programmatic dismissal, bypass the module and call `Banner.dismiss(userId, bannerId)` from `@rocket.chat/core-services` directly.
  3. Make sure the code path that generates the `triggerId` also attaches the acting user to the interaction payload.

Example fix

// before
await bannerModule.viewClosed({
  appId: 'banner-core',
  payload: { view: { viewId: bannerId }, triggerId },
} as any);

// after
await bannerModule.viewClosed({
  appId: 'banner-core',
  user: { _id: userId },
  payload: { view: { viewId: bannerId }, triggerId },
} as any);
Defensive patterns

Strategy: validation

Validate before calling

const canDismiss = (payload: UiKitCoreAppViewClosedPayload): boolean =>
  Boolean(payload.user?._id && payload.payload?.view?.viewId && payload.triggerId);

if (canDismiss(payload)) {
  await bannerModule.viewClosed(payload);
}

Type guard

function isCompleteBannerClosePayload(
  payload: UiKitCoreAppViewClosedPayload,
): payload is UiKitCoreAppViewClosedPayload & { user: { _id: string }; payload: { view: { viewId: string } }; triggerId: string } {
  return Boolean(payload.user?._id && payload.payload?.view?.viewId && payload.triggerId);
}

Try / catch

try {
  await bannerModule.viewClosed(payload);
} catch (error) {
  if (error instanceof Error && error.message === 'invalid user') {
    return; // no user context: nothing to dismiss
  }
  throw error;
}

Prevention

When it happens

Trigger: Dispatching a `viewClosed` interaction to the `banner-core` app where `payload.user` or `payload.user._id` is undefined — e.g. a programmatically built payload, a test double that omits the user, or a client still rendering a banner after logout.

Common situations: Custom server code closing banners by emitting UiKit core events instead of calling `Banner.dismiss`; payload mocks in unit tests; DDP reconnect after logout while a banner surface is still open.

Related errors


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