{"record":{"id":"97e283e8d83029db","repo":"mastra-ai/mastra","slug":"mastrafactory-integration-tool-name-from","errorCode":null,"errorMessage":"MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.","messagePattern":"MastraFactory: integration tool '(.+?)' from '(.+?)' conflicts with '(.+?)'\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/factory.ts","lineNumber":678,"sourceCode":"          factoryOrgUnresolved: true,\n          projectPath: '',\n          projectName: '',\n          gitBranch: '',\n        },\n        storage: storage.getMastraStorage(),\n        ...(mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {}),\n        ...(factoryProcessor ? { inputProcessors: [factoryProcessor] } : {}),\n        ...(vector ? { vector } : {}),\n        ...(toolIntegrations.length > 0 || (workItemsStorage && transitionService)\n          ? {\n              extraTools: async ({ requestContext }: { requestContext: RequestContext }) => {\n                const tools: IntegrationTools = {};\n                const toolOwners = new Map<string, string>();\n                const mergeTools = (ownerId: string, contributed: IntegrationTools) => {\n                  for (const [name, tool] of Object.entries(contributed)) {\n                    const owner = toolOwners.get(name);\n                    if (owner) {\n                      throw new Error(\n                        `MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.`,\n                      );\n                    }\n                    toolOwners.set(name, ownerId);\n                    tools[name] = tool;\n                  }\n                };\n                if (workItemsStorage && transitionService) {\n                  mergeTools(\n                    'factory',\n                    await createFactoryTransitionTools({\n                      requestContext,\n                      storage: workItemsStorage,\n                      transitionService,\n                      // Heals crash-resumed sessions: recovered addresses re-seed\n                      // projectRepositoryId/baseRef from the source session record.\n                      // Only offered while the source-control domain is ready — a\n                      // throwing lookup would abort recovery's catch block and also","sourceCodeStart":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/factory.ts#L660-L696","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rename the tool in your custom integration so its exported name is unique (e.g. prefix with the integration id: githubCreateIssue).","Remove one of the two integrations if they are redundant.","Check the error's owner names to identify which two integrations collide and coordinate the naming.","Namespace all tool names by their integration id as a team convention to prevent future collisions."],"exampleFix":"// before\n// custom-integration.ts\nexport const createIssue = createTool({ ... }); // clashes with github's createIssue\n\n// after\nexport const trackerCreateIssue = createTool({ ... }); // unique name","handlingStrategy":"validation","validationCode":"function assertUniqueToolNames(integrations) {\n  const seen = new Map();\n  for (const integration of integrations) {\n    for (const toolName of Object.keys(integration.tools ?? {})) {\n      if (seen.has(toolName)) throw new Error(`Tool '${toolName}' provided by both '${seen.get(toolName)}' and '${integration.id}'`);\n      seen.set(toolName, integration.id);\n    }\n  }\n}\nassertUniqueToolNames(factoryConfig.integrations ?? []);","typeGuard":"function toolsAreNamespaced(integrations) {\n  return integrations.every(i =>\n    Object.keys(i.tools ?? {}).every(name => name.startsWith(i.id + '_'))\n  );\n}","tryCatchPattern":"try {\n  await factory.prepare();\n} catch (err) {\n  const m = err.message.match(/integration tool '(\\w+)' from '(\\S+)' conflicts with '(\\S+)'/);\n  if (m) throw new Error(`Rename tool '${m[1]}' in '${m[2]}' (already owned by '${m[3]}')`, { cause: err });\n  throw err;\n}","preventionTips":["Prefix every tool name with its integration id (e.g. githubCreateIssue)","Keep a shared registry of reserved tool names","Review new integrations for generic names like 'search' or 'create'","Add a config test that pre-merges tool names and asserts uniqueness"],"tags":["naming-conflict","integration","tools","duplicate"],"backgroundTag":"naming-collision","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}