ComposioHQ/composio · error · ValidationError

Custom tool slug collision: original slug "${handle.slug}" i

Error message

Custom tool slug collision: original slug "${handle.slug}" is already registered for toolkit "${toolkit ?? 'custom'}".

What it means

The custom-tools map keys original slugs per toolkit, and two entries share the same (toolkit, originalSlug) pair. Even if their final slugs differ, the SDK cannot tell which handle a response tool refers to, so it rejects the duplicate original slug with a ValidationError.

Source

Thrown at ts/packages/core/src/models/CustomTool.ts:356

    // Length validated early in createCustomTool/createCustomToolkit, but check as safety net
    if (finalSlug.length > MAX_SLUG_LENGTH) {
      throw new ValidationError(
        `Custom tool slug "${handle.slug}" produces final slug "${finalSlug}" ` +
          `which exceeds ${MAX_SLUG_LENGTH} characters.`
      );
    }

    // Check cross-group collisions on final slug
    if (byFinalSlug.has(finalSlugKey)) {
      throw new ValidationError(
        `Custom tool slug collision: "${finalSlug}" is already registered.`
      );
    }

    const qualifiedKey = qualifiedOriginalSlugKey(toolkit, originalSlug);
    if (byToolkitAndOriginalSlug.has(qualifiedKey)) {
      throw new ValidationError(
        `Custom tool slug collision: original slug "${handle.slug}" is already registered for toolkit "${toolkit ?? 'custom'}".`
      );
    }

    const entry: CustomToolsMapEntry = { handle, finalSlug, toolkit };
    byFinalSlug.set(finalSlugKey, entry);
    byToolkitAndOriginalSlug.set(qualifiedKey, entry);
    addOriginalSlugAlias({ byOriginalSlug, ambiguousOriginalSlugs, originalSlug, entry });
  };

  // Process standalone tools
  for (const handle of tools) {
    addEntry(handle, buildFinalSlug(handle.slug, handle.extendsToolkit), handle.extendsToolkit);
  }

  // Process toolkit tools
  if (toolkits) {
    for (const toolkit of toolkits) {

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Deduplicate the registration — register each (toolkit, slug) pair only once
  2. Give one of the tools a distinct slug within that toolkit
  3. If two toolkits legitimately have the same slug, ensure each declares its own toolkit name so the qualified key differs

Example fix

// before
composio.tools.add(defineTool({ slug: 'syncUser', toolkit: 'crm' }));
composio.tools.add(defineTool({ slug: 'syncUser', toolkit: 'crm' }));
// after
composio.tools.add(defineTool({ slug: 'syncUser', toolkit: 'crm' }));
composio.tools.add(defineTool({ slug: 'archiveUser', toolkit: 'crm' }));
Defensive patterns

Strategy: validation

Validate before calling

const keys = new Set<string>();
for (const t of customTools) {
  const k = `${t.toolkit ?? 'custom'}:${t.slug}`;
  if (keys.has(k)) throw new Error(`Duplicate (toolkit, slug): ${k}`);
  keys.add(k);
}

Type guard

const hasUniqueQualifiedSlugs = (tools: {slug: string; toolkit?: string}[]): boolean =>
  new Set(tools.map(t => `${t.toolkit ?? 'custom'}:${t.slug}`)).size === tools.length;

Try / catch

try { await session.withCustomTools(tools); } catch (e) { if (e instanceof ValidationError && /original slug/.test(e.message)) { /* rename duplicate */ } throw e; }

Prevention

When it happens

Trigger: Registering two custom tools with the same slug inside the same toolkit (or with no toolkit, i.e. the 'custom' pseudo-toolkit) via buildCustomToolsMap; e.g. two defineTool calls with slug 'sendEmail' and no toolkit set.

Common situations: Accidentally registering the same tool twice (e.g. importing a shared definition into two composable configs); merging custom toolkit definitions that each declare the same tool slug; copy-paste of tool definitions without slug changes.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/3c2f4aa3ce95acdb. Report an issue: GitHub.