RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-room

error-invalid-room

Error message

Invalid room

What it means

Thrown by addIncomingIntegration while resolving each channel target. The leading '#' or '@' is stripped (channel.substr(1)), then '#x' is looked up in Rooms by _id or name and '@x' in Users by _id or username. If no document matches, the target does not exist and 'error-invalid-room' is thrown. This is a data lookup failure, not a permission failure.

Source

Thrown at apps/meteor/server/meteor-methods/integrations/incoming/addIncomingIntegration.ts:155

		let record;
		const channelType = channel[0];
		channel = channel.substr(1);

		switch (channelType) {
			case '#':
				record = await Rooms.findOne({
					$or: [{ _id: channel }, { name: channel }],
				});
				break;
			case '@':
				record = await Users.findOne({
					$or: [{ _id: channel }, { username: channel }],
				});
				break;
		}

		if (!record) {
			throw new Meteor.Error('error-invalid-room', 'Invalid room', {
				method: 'addIncomingIntegration',
			});
		}

		if (
			!(await hasAllPermissionAsync(userId, ['manage-incoming-integrations', 'manage-own-incoming-integrations'])) &&
			!(await Subscriptions.findOneByRoomIdAndUserId(record._id, userId, { projection: { _id: 1 } }))
		) {
			throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', {
				method: 'addIncomingIntegration',
			});
		}
	}

	const strippedIntegrationData = removeEmpty(integrationData);

	const { insertedId } = await Integrations.insertOne(strippedIntegrationData);

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Use the exact room name (lowercase slug from the room's URL, without the #) for room targets
  2. Create the room or user first, then create the integration
  3. Verify targets ahead of time from server code with Rooms.findOne({ name }) / Users.findOneByUsername or via GET /api/v1/channels.info

Example fix

// before
Meteor.callAsync('addIncomingIntegration', { ...integration, channel: '#General Chat' }); // no room named 'General Chat'
// after
await Meteor.callAsync('createChannel', 'general-chat', ['rocket.cat']); // ensure the room exists
Meteor.callAsync('addIncomingIntegration', { ...integration, channel: '#general-chat' });
Defensive patterns

Strategy: validation

Validate before calling

// server-side caller: resolve every target before creating the integration
import { Rooms, Users } from '@rocket.chat/models';
for (const entry of integration.channel.split(',')) {
  const type = entry.trim()[0]; const name = entry.trim().slice(1);
  const record = type === '#' ? await Rooms.findOne({ $or: [{ _id: name }, { name }] }) : await Users.findOne({ $or: [{ _id: name }, { username: name }] });
  if (!record) throw new Error(`target does not exist: ${entry.trim()}`);
}
await Meteor.callAsync('addIncomingIntegration', integration);

Try / catch

try {
  await Meteor.callAsync('addIncomingIntegration', integration);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'error-invalid-room') { /* re-validate room list, offer room picker */ }
}

Prevention

When it happens

Trigger: '#genral' (typo); room renamed or deleted between selection and submit; '#General' where the room name is 'general' (names are lowercase, case-sensitive here); '@former.employee' whose account was deleted.

Common situations: Integration forms storing stale room lists; provisioning scripts that create integrations before creating the channels; users typing display names like 'General Chat' instead of the slug 'general-chat'.

Related errors


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