mastra-ai/mastra · error

MastraFactory: integration tool '${name}' from '${ownerId}'

Error message

MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.

What it means

Each integration contributes named tools; mergeTools tracks the owner of every tool name in a Map and throws when a second integration tries to register a tool name already claimed by another. Without this check the later integration would silently overwrite the earlier one's tool, changing agent behavior. The error names the tool, the conflicting integration id, and the original owner.

Source

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

          factoryOrgUnresolved: true,
          projectPath: '',
          projectName: '',
          gitBranch: '',
        },
        storage: storage.getMastraStorage(),
        ...(mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {}),
        ...(factoryProcessor ? { inputProcessors: [factoryProcessor] } : {}),
        ...(vector ? { vector } : {}),
        ...(toolIntegrations.length > 0 || (workItemsStorage && transitionService)
          ? {
              extraTools: async ({ requestContext }: { requestContext: RequestContext }) => {
                const tools: IntegrationTools = {};
                const toolOwners = new Map<string, string>();
                const mergeTools = (ownerId: string, contributed: IntegrationTools) => {
                  for (const [name, tool] of Object.entries(contributed)) {
                    const owner = toolOwners.get(name);
                    if (owner) {
                      throw new Error(
                        `MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.`,
                      );
                    }
                    toolOwners.set(name, ownerId);
                    tools[name] = tool;
                  }
                };
                if (workItemsStorage && transitionService) {
                  mergeTools(
                    'factory',
                    await createFactoryTransitionTools({
                      requestContext,
                      storage: workItemsStorage,
                      transitionService,
                      // Heals crash-resumed sessions: recovered addresses re-seed
                      // projectRepositoryId/baseRef from the source session record.
                      // Only offered while the source-control domain is ready — a
                      // throwing lookup would abort recovery's catch block and also

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Rename the tool in your custom integration so its exported name is unique (e.g. prefix with the integration id: githubCreateIssue).
  2. Remove one of the two integrations if they are redundant.
  3. Check the error's owner names to identify which two integrations collide and coordinate the naming.
  4. Namespace all tool names by their integration id as a team convention to prevent future collisions.

Example fix

// before
// custom-integration.ts
export const createIssue = createTool({ ... }); // clashes with github's createIssue

// after
export const trackerCreateIssue = createTool({ ... }); // unique name
Defensive patterns

Strategy: validation

Validate before calling

function assertUniqueToolNames(integrations) {
  const seen = new Map();
  for (const integration of integrations) {
    for (const toolName of Object.keys(integration.tools ?? {})) {
      if (seen.has(toolName)) throw new Error(`Tool '${toolName}' provided by both '${seen.get(toolName)}' and '${integration.id}'`);
      seen.set(toolName, integration.id);
    }
  }
}
assertUniqueToolNames(factoryConfig.integrations ?? []);

Type guard

function toolsAreNamespaced(integrations) {
  return integrations.every(i =>
    Object.keys(i.tools ?? {}).every(name => name.startsWith(i.id + '_'))
  );
}

Try / catch

try {
  await factory.prepare();
} catch (err) {
  const m = err.message.match(/integration tool '(\w+)' from '(\S+)' conflicts with '(\S+)'/);
  if (m) throw new Error(`Rename tool '${m[1]}' in '${m[2]}' (already owned by '${m[3]}')`, { cause: err });
  throw err;
}

Prevention

When it happens

Trigger: Two integrations in config.integrations both register a tool with the same exported name — e.g. both a custom integration and a built-in export a tool called 'createIssue', so the second merge call hits an existing owner in toolOwners.

Common situations: Building a custom integration that reuses a generic tool name like 'search' or 'createTicket' that a built-in integration already uses; enabling two integrations that both wrap the same upstream API; renaming an integration id without renaming its tool names.

Related errors


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