mastra-ai/mastra · error

MastraFactory: integrations [${channelRegistrations.map(({ i

Error message

MastraFactory: integrations [${channelRegistrations.map(({ integration }) => integration.id).join(', ')}] all provide channels, but only one may. Remove all but one.

What it means

Integrations can provide a channel (the messaging transport that receives conversations). The factory installs channels via setChannels, which replaces rather than merges, so a second channel-providing integration would silently never receive messages. To avoid that, prepare() collects all ready integrations exposing channels and throws if more than one is present, listing their ids.

Source

Thrown at mastracode/factory/src/factory.ts:919

      { blocking: true },
    );

    this.#prepared = prepared;
    this.#factoryProcessor = factoryProcessor;

    // Chat-platform channels (Slack, Discord, …) contributed by integrations,
    // attached to the mounted controller so inbound platform messages reach
    // the same agents the web UI drives. READY integrations only — readiness
    // means the `channel-identity` domain's `init()` succeeded, so its link
    // table is queryable. Without it a sender can't be resolved to a tenant,
    // and attaching anyway would dispatch runs on default credentials.
    const channelRegistrations = integrationRegistrations.filter(
      ({ integration, ready }) => ready && integration.channels,
    );
    // `setChannels` replaces rather than merges, so a second provider would
    // silently never receive a message. Fail loud instead.
    if (channelRegistrations.length > 1) {
      throw new Error(
        `MastraFactory: integrations [${channelRegistrations
          .map(({ integration }) => integration.id)
          .join(', ')}] all provide channels, but only one may. Remove all but one.`,
      );
    }
    for (const { integration } of channelRegistrations) {
      // Integrations return a channels CONFIG; the factory owns construction.
      prepared.base.controller.setChannels(
        new AgentControllerChannels(
          integration.channels!(
            buildIntegrationContext(
              {
                controller: prepared.base.controller,
                publicOrigin,
                auth: routeAuth,
                stateSigner,
                sandbox: sandboxConfig,
                factoryStorage: storage,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Keep exactly one channel-providing integration in the config; remove the others.
  2. If you need multiple transports, route them behind a single channel-providing integration rather than registering several.
  3. Ensure helper integrations do not set the channels property if they are not meant to own the transport.
  4. Check the readiness logic — an integration that should be inactive may be reporting ready and contributing channels.

Example fix

// before
integrations: [slackIntegration, discordChannelIntegration] // both provide channels

// after
integrations: [slackIntegration] // single channel provider
Defensive patterns

Strategy: validation

Validate before calling

const channelProviders = (factoryConfig.integrations ?? []).filter(i => i.channels != null);
if (channelProviders.length > 1) {
  throw new Error(`Only one integration may provide channels; found: ${channelProviders.map(i => i.id).join(', ')}`);
}

Type guard

function hasSingleChannelProvider(integrations) {
  return integrations.filter(i => i.channels != null).length <= 1;
}

Try / catch

try {
  await factory.prepare();
} catch (err) {
  if (err.message.includes('all provide channels, but only one may')) {
    const ids = err.message.match(/\[(.*)\]/)?.[1];
    throw new Error(`Remove all but one channel provider (found: ${ids})`, { cause: err });
  }
  throw err;
}

Prevention

When it happens

Trigger: Registering two or more integrations that each define integration.channels (and are 'ready') in the factory config — e.g. both a Slack integration and a custom Discord integration providing channels.

Common situations: Adding a second chat integration while forgetting to remove or disable the first; testing a new channel integration alongside the production one; an integration incorrectly marking itself as providing channels when it was meant to only consume them.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/ba3b8d66e3e53cd2. Report an issue: GitHub.