{"record":{"id":"ba3b8d66e3e53cd2","repo":"mastra-ai/mastra","slug":"mastrafactory-integrations-channelregistration","errorCode":null,"errorMessage":"MastraFactory: integrations [${channelRegistrations.map(({ integration }) => integration.id).join(', ')}] all provide channels, but only one may. Remove all but one.","messagePattern":"MastraFactory: integrations \\[(.+?)\\) => integration\\.id\\)\\.join\\(', '\\)\\}\\] all provide channels, but only one may\\. Remove all but one\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/factory.ts","lineNumber":919,"sourceCode":"      { blocking: true },\n    );\n\n    this.#prepared = prepared;\n    this.#factoryProcessor = factoryProcessor;\n\n    // Chat-platform channels (Slack, Discord, …) contributed by integrations,\n    // attached to the mounted controller so inbound platform messages reach\n    // the same agents the web UI drives. READY integrations only — readiness\n    // means the `channel-identity` domain's `init()` succeeded, so its link\n    // table is queryable. Without it a sender can't be resolved to a tenant,\n    // and attaching anyway would dispatch runs on default credentials.\n    const channelRegistrations = integrationRegistrations.filter(\n      ({ integration, ready }) => ready && integration.channels,\n    );\n    // `setChannels` replaces rather than merges, so a second provider would\n    // silently never receive a message. Fail loud instead.\n    if (channelRegistrations.length > 1) {\n      throw new Error(\n        `MastraFactory: integrations [${channelRegistrations\n          .map(({ integration }) => integration.id)\n          .join(', ')}] all provide channels, but only one may. Remove all but one.`,\n      );\n    }\n    for (const { integration } of channelRegistrations) {\n      // Integrations return a channels CONFIG; the factory owns construction.\n      prepared.base.controller.setChannels(\n        new AgentControllerChannels(\n          integration.channels!(\n            buildIntegrationContext(\n              {\n                controller: prepared.base.controller,\n                publicOrigin,\n                auth: routeAuth,\n                stateSigner,\n                sandbox: sandboxConfig,\n                factoryStorage: storage,","sourceCodeStart":901,"sourceCodeEnd":937,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/factory.ts#L901-L937","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nintegrations: [slackIntegration, discordChannelIntegration] // both provide channels\n\n// after\nintegrations: [slackIntegration] // single channel provider","handlingStrategy":"validation","validationCode":"const channelProviders = (factoryConfig.integrations ?? []).filter(i => i.channels != null);\nif (channelProviders.length > 1) {\n  throw new Error(`Only one integration may provide channels; found: ${channelProviders.map(i => i.id).join(', ')}`);\n}","typeGuard":"function hasSingleChannelProvider(integrations) {\n  return integrations.filter(i => i.channels != null).length <= 1;\n}","tryCatchPattern":"try {\n  await factory.prepare();\n} catch (err) {\n  if (err.message.includes('all provide channels, but only one may')) {\n    const ids = err.message.match(/\\[(.*)\\]/)?.[1];\n    throw new Error(`Remove all but one channel provider (found: ${ids})`, { cause: err });\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["configuration","integration","channels","conflict"],"backgroundTag":"multiple-providers-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}