RocketChat/Rocket.Chat · error · Error
invalid triggerId
Error message
invalid triggerId
What it means
`BannerModule.viewClosed` (`banner-core`) throws `Error('invalid triggerId')` when `payload.triggerId` is falsy. The triggerId routes the server interaction response (`banner.close`) back to the client surface that opened the banner, so without it the response has nowhere to go and the guard fires before `Banner.dismiss` runs.
Source
Thrown at apps/meteor/server/modules/core-apps/banner.module.ts:24
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
- Forward the `triggerId` generated by the originating banner surface in the viewClosed payload.
- If you only need server-side dismissal (no response surface), call `Banner.dismiss(userId, bannerId)` directly — no triggerId is required.
Example fix
// before
await bannerModule.viewClosed({ appId: 'banner-core', user: { _id: userId }, payload: { view: { viewId } } } as any);
// after
await bannerModule.viewClosed({ appId: 'banner-core', user: { _id: userId }, payload: { view: { viewId } }, triggerId } as any); Defensive patterns
Strategy: validation
Validate before calling
if (!payload.triggerId) {
throw new Error('triggerId is required for banner close interactions');
}
await bannerModule.viewClosed(payload); Type guard
const hasTriggerId = (
payload: UiKitCoreAppViewClosedPayload,
): payload is UiKitCoreAppViewClosedPayload & { triggerId: string } =>
typeof payload.triggerId === 'string' && payload.triggerId.length > 0; Try / catch
try {
await bannerModule.viewClosed(payload);
} catch (error) {
if (error instanceof Error && error.message === 'invalid triggerId') {
// dismiss directly instead: await Banner.dismiss(userId, bannerId)
}
} Prevention
- Forward the triggerId issued with the banner surface
- For server-side dismissal with no UI, call Banner.dismiss directly
- Do not strip trigger fields when logging or serializing payloads
When it happens
Trigger: Dispatching a banner close interaction without a `triggerId` — e.g. synthetic interactions built by hand, payloads replayed from logs with the field stripped, or an old client version that does not forward the trigger.
Common situations: Programmatic dismissal attempts that mimic the UI payload incompletely; test harnesses that omit trigger metadata; middleware or serializers that drop falsy-looking fields.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/49a1dc8e4984393a.
Report an issue: GitHub.