{"record":{"id":"ee5b10d4c5f299ba","repo":"ComposioHQ/composio","slug":"custom-tool-slug-collision-finalslug-is-alre","errorCode":null,"errorMessage":"Custom tool slug collision: \"${finalSlug}\" is already registered.","messagePattern":"Custom tool slug collision: \"(.+?)\" is already registered\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/CustomTool.ts","lineNumber":349,"sourceCode":"  const byToolkitAndOriginalSlug = new Map<string, CustomToolsMapEntry>();\n  const ambiguousOriginalSlugs = new Set<string>();\n\n  const addEntry = (handle: CustomTool, finalSlug: string, toolkit?: string) => {\n    const originalSlug = handle.slug.toUpperCase();\n    // Custom tool slugs are matched case-insensitively across local and response maps.\n    const finalSlugKey = finalSlug.toUpperCase();\n\n    // Length validated early in createCustomTool/createCustomToolkit, but check as safety net\n    if (finalSlug.length > MAX_SLUG_LENGTH) {\n      throw new ValidationError(\n        `Custom tool slug \"${handle.slug}\" produces final slug \"${finalSlug}\" ` +\n          `which exceeds ${MAX_SLUG_LENGTH} characters.`\n      );\n    }\n\n    // Check cross-group collisions on final slug\n    if (byFinalSlug.has(finalSlugKey)) {\n      throw new ValidationError(\n        `Custom tool slug collision: \"${finalSlug}\" is already registered.`\n      );\n    }\n\n    const qualifiedKey = qualifiedOriginalSlugKey(toolkit, originalSlug);\n    if (byToolkitAndOriginalSlug.has(qualifiedKey)) {\n      throw new ValidationError(\n        `Custom tool slug collision: original slug \"${handle.slug}\" is already registered for toolkit \"${toolkit ?? 'custom'}\".`\n      );\n    }\n\n    const entry: CustomToolsMapEntry = { handle, finalSlug, toolkit };\n    byFinalSlug.set(finalSlugKey, entry);\n    byToolkitAndOriginalSlug.set(qualifiedKey, entry);\n    addOriginalSlugAlias({ byOriginalSlug, ambiguousOriginalSlugs, originalSlug, entry });\n  };\n\n  // Process standalone tools","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/CustomTool.ts#L331-L367","documentation":"While building the internal custom-tools map, Composio detected that two custom tools resolve to the same final slug (compared case-insensitively). The SDK registers every custom tool under its final slug and cannot disambiguate duplicates, so it throws a ValidationError during map construction rather than failing later at execution time.","triggerScenarios":"Registering two custom tools (via Composio custom tool definitions or custom toolkits) whose slugs collide after normalization — e.g. 'My_Tool' and 'my-tool', or a custom tool whose slug matches a toolkit-prefixed slug that reduces to the same final key. Surfaced from buildCustomToolsMap / buildCustomToolsMapFromResponse.","commonSituations":"Copying a custom tool definition and forgetting to change the slug; mixing SDK custom tools with tools loaded from a response that share names; case differences in slugs across environments; adding a tool to a toolkit that already exposes a similarly-named tool.","solutions":["Uniquely rename the colliding custom tool slug (check case-insensitive equality)","List all registered custom tool slugs and diff them to find the duplicate pair","If the duplicate comes from a custom toolkit, change the toolkit name or tool slug so the final slug differs"],"exampleFix":"// before\nconst tools = [defineTool({ slug: 'fetchPage' }), defineTool({ slug: 'fetch_page' })];\n// after\nconst tools = [defineTool({ slug: 'fetchPage' }), defineTool({ slug: 'extractLinks' })];","handlingStrategy":"validation","validationCode":"const final = (slug) => slug.toUpperCase(); // approximate SDK normalization\nconst seen = new Set<string>();\nfor (const t of customTools) {\n  const key = final(t.slug);\n  if (seen.has(key)) throw new Error(`Duplicate final slug: ${t.slug}`);\n  seen.add(key);\n}","typeGuard":"const isUniqueFinalSlugs = (tools: {slug: string}[]): boolean =>\n  new Set(tools.map(t => t.slug.toUpperCase())).size === tools.length;","tryCatchPattern":"try { buildCustomToolsMap(tools); } catch (e) { if (e instanceof ValidationError && e.message.includes('slug collision')) { /* dedupe and retry */ } throw e; }","preventionTips":["Keep a single module exporting all custom tool slugs to review for collisions","Treat slugs as case-insensitive unique identifiers in code review","Add a startup assertion that all final slugs are unique"],"tags":["custom-tools","slug-collision","validation","typescript"],"backgroundTag":"duplicate-resource-identifier","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}