mastra-ai/mastra · error
Code Mode tool id collision: "${prior}" and "${toolId}" both
Error message
Code Mode tool id collision: "${prior}" and "${toolId}" both map to external_${externalName} What it means
The stub generator emits TypeScript declarations `declare function external_<name>(...)` for each tool exposed to Code Mode. If two tool ids sanitize to the same external name, the generated stubs would collide (duplicate declarations pointing at different tools), so generateStubs throws with both offending ids.
Source
Thrown at packages/core/src/tools/code-mode/stub-generator.ts:178
declaration: string;
}
/** Generate stubs for every tool in the config. */
export function generateStubs(tools: ToolsInput): CodeModeStub[] {
// Two distinct tool ids can sanitize to the same `external_*` name (e.g.
// `a-b` and `a_b`). Without this check the later binding would silently
// overwrite the earlier one in the runner, so fail fast instead.
const seen = new Map<string, string>();
return Object.entries(tools).map(([key, tool]) => {
const toolId = (tool as { id?: string }).id ?? key;
const description = (tool as { description?: string }).description;
const inputType = schemaToTs((tool as { inputSchema?: unknown }).inputSchema, 'input');
const outputType = schemaToTs((tool as { outputSchema?: unknown }).outputSchema, 'output');
const externalName = sanitizeToolId(toolId);
const prior = seen.get(externalName);
if (prior !== undefined && prior !== toolId) {
throw new Error(`Code Mode tool id collision: "${prior}" and "${toolId}" both map to external_${externalName}`);
}
seen.set(externalName, toolId);
const doc = description ? `/** ${description.replace(/\*\//g, '* /')} */\n` : '';
const declaration = `${doc}declare function external_${externalName}(input: ${inputType}): Promise<${outputType}>;`;
return { toolId, externalName, declaration };
});
}
const createUsageContract = (toolId: string) => `# Code Mode
You have access to the \`${toolId}\` tool. Instead of calling tools one at a time,
write a single TypeScript program that orchestrates them and returns one result.
Rules:
- Call the available tools via the \`external_*\` functions declared below. Each
returns a Promise — \`await\` it.View on GitHub (pinned to 75dd419e61)
Solutions
- Rename one of the colliding tool ids.
- Deduplicate sanitized names in your tool-registry code before constructing the agent's tool map.
- Standardize on camelCase ids for all Code Mode tools.
Example fix
// before
{ 'report-pdf': t1, 'report_pdf': t2 }
// after
{ 'reportPdf': t1, 'reportPdfV2': t2 } Defensive patterns
Strategy: validation
Validate before calling
const seen = new Set<string>();
for (const id of Object.keys(tools)) {
const ext = sanitizeToolId(id);
if (seen.has(ext)) throw new Error(`Duplicate stub name external_${ext} from "${id}"`);
seen.add(ext);
} Try / catch
try {
stubs = generateStubs(tools);
} catch (e) {
if (String(e.message).includes('tool id collision')) {
// rename colliding ids listed in the message
} else throw e;
} Prevention
- Deduplicate sanitized external names before generating stubs.
- Keep a central tool-id registry with uniqueness checks.
- Rename legacy ids when importing tools from multiple packages.
When it happens
Trigger: Generating stubs for a tool set where two ids map to the same sanitized external name, e.g. 'report-pdf' and 'report_pdf' both -> 'report_pdf'.
Common situations: Mixed kebab/snake-case tool naming; merging tool maps from several sources without deduplication; refactors that renamed ids only in punctuation.
Related errors
- Code Mode external identifier collision: tools "${existing}"
- Invalid Code Mode external identifier: ${externalName}
- IsolatedVmCodeModeTransport requires the --no-node-snapshot
- Project name must be 1-214 lowercase characters, start with
- Worker "${name}" not found. Available: ${this.#workers.map(w
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/3a959dc6bfa65591.
Report an issue: GitHub.