mastra-ai/mastra · error
MastraFactory: integrations
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.
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
- AgentControllerChannels is not bound to an AgentController…
- Channel " " is not registered. Available
- Channels require storage to be configured on the Mastra…
- MastraFactory: duplicate integration id
- No adapter for platform
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ba3b8d66e3e53cd2.
Report an issue: GitHub.
Appendix: 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)