RocketChat/Rocket.Chat · error · Error

error-invalid-channel

Error message

error-invalid-channel

What it means

Thrown by runMergeContacts when the original contact exists but none of its channels match the supplied visitor association (isSameChannel returns false for every channel). The merge is driven by a channel, so without a matching channel the code cannot reconcile the visitor with the contact and aborts inside the transaction.

Source

Thrown at apps/meteor/ee/server/patches/mergeContacts.ts:25

import { notifyOnSettingChanged } from '../../../server/lib/notifyListener';
import { ContactMerger } from '../../../server/lib/omnichannel/contacts/ContactMerger';
import { mergeContacts } from '../../../server/lib/omnichannel/contacts/mergeContacts';
import { contactLogger as logger } from '../lib/omnichannel/logger';

export const runMergeContacts = async (
	_next: any,
	contactId: string,
	visitor: ILivechatContactVisitorAssociation,
	session?: ClientSession,
): Promise<ILivechatContact | null> => {
	const originalContact = await LivechatContacts.findOneEnabledById(contactId, { session });
	if (!originalContact) {
		throw new Error('error-invalid-contact');
	}

	const channel = originalContact.channels.find((channel: ILivechatContactChannel) => isSameChannel(channel.visitor, visitor));
	if (!channel) {
		throw new Error('error-invalid-channel');
	}

	logger.debug({ msg: 'Getting similar contacts', contactId });

	const similarContacts: ILivechatContact[] = await LivechatContacts.findSimilarVerifiedContacts(channel, contactId, { session });

	if (!similarContacts.length) {
		logger.debug({ msg: 'No similar contacts found', contactId });
		return originalContact;
	}

	logger.debug({ msg: 'Found contacts to merge', contactId, count: similarContacts.length });
	for await (const similarContact of similarContacts) {
		const fields = ContactMerger.getAllFieldsFromContact(similarContact);
		await ContactMerger.mergeFieldsIntoContact({ fields, contact: originalContact, session });
	}

	const similarContactIds = similarContacts.map((c) => c._id);

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Verify the visitor's source and visitorId match a channel.visitor on the contact before calling mergeContacts.
  2. If the channel is missing, add/verify it on the contact first via the contact-channel verification flow.
  3. Ensure isSameChannel semantics (source + visitorId) are consistent with how the channel was originally created.
  4. Log the supplied visitor vs the contact.channels to find the mismatch.
Defensive patterns

Strategy: validation

Validate before calling

function findMatchingChannel(contact: ILivechatContact, visitor: ILivechatContactVisitorAssociation): ILivechatContactChannel | undefined {
  return contact.channels.find((ch) => isSameChannel(ch.visitor, visitor));
}
// if (!findMatchingChannel(contact, visitor)) throw new Error('Visitor does not match any channel');

Try / catch

try {
  await mergeContacts(contactId, visitor, session);
} catch (e) {
  if (e.message === 'error-invalid-channel') {
    // verify/add the channel before retrying the merge
  } else throw e;
}

Prevention

When it happens

Trigger: Calling mergeContacts with a visitor whose source/visitorId/token does not line up with any channel.visitor on the contact; visitor came from a different channel type than any channel on the contact.

Common situations: Visitor association built with the wrong source type (e.g. 'api' vs 'web'); visitorId changed after a reconnect; contact channel was unverified/removed; cross-channel merge attempted where no channel overlap exists.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/929ab0d868923d7e. Report an issue: GitHub.