RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-channel-start-with-chars

error-invalid-channel-start-with-chars

Error message

Invalid channel. Start with @ or #

What it means

Thrown in validateOutgoing when a parsed CSV channel entry neither starts with '@' or '#' nor is one of the scoped tokens ('all_public_channels', 'all_private_groups', 'all_direct_messages'). The prefix tells the server how to resolve the entry — '#' for rooms, '@' for direct-message users — so a bare name is ambiguous and rejected with Meteor.Error code 'error-invalid-channel-start-with-chars'.

Source

Thrown at apps/meteor/server/lib/integrations/lib/validateOutgoingIntegration.ts:133

	if (integration.channel && Match.test(integration.channel, String) && integration.channel.trim() === '') {
		delete integration.channel;
	}

	// Moved to it's own function to satisfy the complexity rule
	_verifyRequiredFields(integration);

	let channels: string[] = [];
	if (outgoingEvents[integration.event].use.channel) {
		if (!Match.test(integration.channel, String)) {
			throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', {
				function: 'validateOutgoing',
			});
		} else {
			channels = parseCSV(integration.channel);

			for (const channel of channels) {
				if (!validChannelChars.includes(channel[0]) && !scopedChannels.includes(channel.toLowerCase())) {
					throw new Meteor.Error('error-invalid-channel-start-with-chars', 'Invalid channel. Start with @ or #', {
						function: 'validateOutgoing',
					});
				}
			}
		}
	} else if (!(await hasPermissionAsync(userId, 'manage-outgoing-integrations'))) {
		throw new Meteor.Error('error-invalid-permissions', 'Invalid permission for required Integration creation.', {
			function: 'validateOutgoing',
		});
	}

	const user = await Users.findOne({ username: integration.username });

	if (!user) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user (did you delete the `rocket.cat` user?)', { function: 'validateOutgoing' });
	}

	const integrationData: IOutgoingIntegration = {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Prefix every entry: '#room-name' for channels, '@username' for DMs, e.g. channel: '#general, @alice'
  2. Use the exact scoped tokens all_public_channels / all_private_groups / all_direct_messages for workspace-wide scopes
  3. Normalize input before submitting: csv.split(',').map((s) => s.trim()).filter(Boolean)

Example fix

// before
{ channel: 'general, dev-team' }

// after
{ channel: '#general, #dev-team' }
Defensive patterns

Strategy: validation

Validate before calling

const SCOPED = ['all_public_channels', 'all_private_groups', 'all_direct_messages'];
for (const ch of parseCSV(channel)) {
  if (!['@', '#'].includes(ch[0]) && !SCOPED.includes(ch.toLowerCase())) {
    throw new RangeError(`channel '${ch}' must start with # or @ (or be a scoped token)`);
  }
}

Prevention

When it happens

Trigger: integrations.create with channel 'general, dev-team' (no prefixes), 'all public channels' (spelled out instead of the token), or a CSV entry that is an empty string after splitting a trailing comma (channel[0] is undefined).

Common situations: Users typing plain room names because the UI hint is missed; data migrated from other chat tools where channels have no sigil; the scoped-token comparison is lowercased but the prefix check is not, so '#General' is fine but 'General' fails.

Related errors


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