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
- Prefix every entry: '#room-name' for channels, '@username' for DMs, e.g. channel: '#general, @alice'
- Use the exact scoped tokens all_public_channels / all_private_groups / all_direct_messages for workspace-wide scopes
- 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
- Auto-prefix user input: '# ' for names typed without a sigil in the UI
- Filter empty CSV entries so a trailing comma cannot produce a bogus entry
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
- history-data-must-be-defined
- error-invalid-event-type
- error-invalid-username
- error-invalid-targetRoom
- error-invalid-urls
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/3d375b74428ded6b.
Report an issue: GitHub.