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
- Keep exactly one channel-providing integration in the config; remove the others.
- If you need multiple transports, route them behind a single channel-providing integration rather than registering several.
- Ensure helper integrations do not set the channels property if they are not meant to own the transport.
- 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
- Decide on one channel integration per deployment and gate the rest behind config flags
- Ensure consumer-only integrations do not set the channels property
- Track channel ownership in a central config module
- Add an integration test asserting hasSingleChannelProvider on the production config
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
- MastraFactory: duplicate integration id '${integration.id}'
- Channels require storage to be configured on the Mastra inst
- No adapter for platform "${platform}"
- AgentControllerChannels is not bound to an AgentController.
- Channel "${platform}" is not registered. Available: ${availa
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ba3b8d66e3e53cd2.
Report an issue: GitHub.