mastra-ai/mastra · error

Code Mode external identifier collision: tools "${existing}"

Error message

Code Mode external identifier collision: tools "${existing}" and "${toolId}" both map to external_${externalName}

What it means

When building the runner, two distinct tool ids can sanitize to the same external name (e.g. 'a-b' and 'a_b' both become 'a_b'), which would make the second global overwrite the first and leave one tool unreachable. buildRunner fails fast with this collision error instead.

Source

Thrown at packages/core/src/tools/code-mode/runner.ts:60

/**
 * Produce the full runner source to write into the sandbox and run with node.
 */
export function buildRunner({ programModule, externals }: BuildRunnerOptions): string {
  // `buildRunner` is exported, so a caller could pass a non-sanitized name.
  // External names become global property suffixes, so reject anything that
  // isn't a legal identifier instead of producing an unusable global.
  const SAFE_IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
  const seen = new Map<string, string>();
  for (const { externalName, toolId } of externals) {
    if (!SAFE_IDENT.test(externalName)) {
      throw new Error(`Invalid Code Mode external identifier: ${externalName}`);
    }
    // Two tool ids can sanitize to the same external name (e.g. `a-b` and
    // `a_b` both become `a_b`). The install loop below would silently overwrite
    // the earlier global, leaving one tool unreachable. Fail fast instead.
    const existing = seen.get(externalName);
    if (existing) {
      throw new Error(
        `Code Mode external identifier collision: tools "${existing}" and "${toolId}" both map to external_${externalName}`,
      );
    }
    seen.set(externalName, toolId);
  }

  // Externals are emitted as JSON data, not interpolated identifiers. The
  // runner installs each `external_<name>` global in a loop using bracket
  // assignment, so no caller-derived string is ever spliced into the generated
  // source as code. This keeps tool ids strictly data, even if `sanitize`
  // changes.
  const externalsJson = JSON.stringify(externals.map(({ externalName, toolId }) => ({ externalName, toolId })));

  return `'use strict';
const FRAME_PREFIX = ${JSON.stringify(FRAME_PREFIX)};

function __emit(frame) {
  process.stdout.write(FRAME_PREFIX + JSON.stringify(frame) + '\\n');

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Rename one of the colliding tool ids so their sanitized external names differ.
  2. Adopt a single naming convention (e.g. camelCase) for tool ids used with Code Mode.
  3. Pre-check sanitized names for duplicates before passing the tool map to createCodeMode.

Example fix

// before
createCodeMode({ tools: { 'get-user': a, 'get_user': b } });
// after
createCodeMode({ tools: { 'getUser': a, 'get_user_v2': b } });
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Map<string, string>();
for (const id of Object.keys(tools)) {
  const ext = sanitizeToolId(id);
  if (seen.has(ext)) throw new Error(`Collision: "${seen.get(ext)}" and "${id}" -> external_${ext}`);
  seen.set(ext, id);
}

Try / catch

try {
  codeMode = createCodeMode({ tools });
} catch (e) {
  if (String(e.message).includes('external identifier collision')) {
    // rename one of the tools named in the message and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Registering two tools in createCodeMode whose ids differ only by characters sanitized to underscores, e.g. 'get-data' and 'get_data'.

Common situations: Teams mixing kebab-case and snake_case tool names; importing tools from multiple packages with overlapping names; programmatically generated ids that differ only in punctuation.

Related errors


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