{"record":{"id":"abaab51f2eca455a","repo":"ComposioHQ/composio","slug":"ambiguous-custom-tool-slug-slug-multiple-cus","errorCode":null,"errorMessage":"Ambiguous custom tool slug \"${slug}\". Multiple custom toolkit tools share this original slug; manual session.execute() by original slug is only supported when the original slug is unique.${hint}","messagePattern":"Ambiguous custom tool slug \"(.+?)\"\\. Multiple custom toolkit tools share this original slug; manual session\\.execute\\(\\) by original slug is only supported when the original slug is unique\\.(.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/customToolExecution.ts","lineNumber":49,"sourceCode":" * Reject ambiguous bare original slugs before falling through to backend execution.\n * Agents receive final slugs (LOCAL_<TOOLKIT>_<TOOL>) from schemas, while bare original\n * slugs are only a convenience for manual session.execute() calls when unique.\n */\nexport function assertUnambiguousCustomToolSlug(\n  map: CustomToolsMap | undefined,\n  slug: string\n): void {\n  if (!map) return;\n  const upper = slug.toUpperCase();\n  if (!map.ambiguousOriginalSlugs?.has(upper)) return;\n\n  const finalSlugs = [...map.byFinalSlug.values()]\n    .filter(entry => entry.handle.slug.toUpperCase() === upper)\n    .map(entry => entry.finalSlug)\n    .sort();\n  const hint = finalSlugs.length ? ` Use one of: ${finalSlugs.join(', ')}.` : '';\n\n  throw new ValidationError(\n    `Ambiguous custom tool slug \"${slug}\". Multiple custom toolkit tools share this original slug; ` +\n      `manual session.execute() by original slug is only supported when the original slug is unique.` +\n      hint\n  );\n}\n\n/**\n * Execute a custom tool in-process.\n * Validates input via the Zod schema, calls the user's execute function,\n * and wraps the result into the standard response format.\n *\n * Callers provide a pre-built SessionContext (which may include sibling routing).\n */\nexport async function executeCustomTool(\n  entry: CustomToolsMapEntry,\n  arguments_: Record<string, unknown>,\n  sessionContext: SessionContext,\n  options?: { signal?: AbortSignal }","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/customToolExecution.ts#L31-L67","documentation":"When custom toolkits register tools, each gets a final namespaced slug (LOCAL_<TOOLKIT>_<TOOL>) while the original slug stays usable for manual session.execute() only if unique. If two custom toolkits expose the same original slug (case-insensitive), that slug is marked ambiguous and assertUnambiguousCustomToolSlug throws ValidationError to prevent executing an arbitrary toolkit's tool.","triggerScenarios":"Registering two custom toolkits that both define a tool with the same original slug (e.g. 'GET_DATA'), then calling session.execute('GET_DATA') / routing execution with the bare original slug. The error is thrown by execute, routingExecuteFn, and routeMultiExecute before backend execution.","commonSituations":"Composing multiple third-party/custom toolkits that happen to reuse generic tool names, refactoring one toolkit into two while keeping tool names, or hardcoded slug strings in app code after adding a second toolkit.","solutions":["Use the fully-qualified final slug (LOCAL_<TOOLKIT>_<TOOL>) listed in the error hint","Rename one of the colliding tools in your custom toolkit definition so original slugs are unique","Dynamically read final slugs from the tool schemas given to the agent instead of hardcoding bare slugs"],"exampleFix":"// before\nawait session.execute('GET_DATA', { userId });\n// after (use the namespaced final slug from the error hint)\nawait session.execute('LOCAL_REPORTS_GET_DATA', { userId });","handlingStrategy":"validation","validationCode":"const finalSlug = toolSchemas.find(t => t.slug.toUpperCase().endsWith('_GET_DATA'))?.slug;\nif (!finalSlug) throw new Error('No unique tool found');\nawait session.execute(finalSlug, args);","typeGuard":"const isUnambiguousSlug = (map: CustomToolsMap | undefined, slug: string): boolean =>\n  !map || !map.ambiguousOriginalSlugs?.has(slug.toUpperCase());","tryCatchPattern":"try { await session.execute(slug, args); } catch (e) { if (e instanceof ValidationError && e.message.includes('Ambiguous custom tool slug')) { /* pick final slug from hint */ } throw e; }","preventionTips":["Prefer final namespaced slugs (LOCAL_<TOOLKIT>_<TOOL>) in code","Keep custom tool slugs unique across toolkits","Derive slugs from tool schemas rather than hardcoding"],"tags":["custom-tools","slug-collision","validation","typescript"],"backgroundTag":"ambiguous-identifier-collision","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}