mastra-ai/mastra · error
Invalid Code Mode external identifier: ${externalName}
Error message
Invalid Code Mode external identifier: ${externalName} What it means
Code Mode exposes each tool to sandboxed code as a global named external_<sanitizedToolId>. buildRunner validates each sanitized external name against /^[A-Za-z_$][A-Za-z0-9_$]*$/ because it becomes a global property suffix; an invalid identifier would produce unusable code, so buildRunner throws.
Source
Thrown at packages/core/src/tools/code-mode/runner.ts:53
* Top-level `return`, `await`, and `const` work because the body lives inside
* an async function.
*/
export function buildProgramModule(program: string): string {
return `export default async function () {\n${program}\n}\n`;
}
/**
* 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.View on GitHub (pinned to 75dd419e61)
Solutions
- Rename the tool id so it sanitizes to a legal identifier (start with a letter, $, or _).
- If tool ids come from user input, validate them against a safe-identifier pattern at registration time.
- Prefix numeric-leading ids (e.g. 'tool-123' instead of '123').
Example fix
// before
createCodeMode({ tools: { '123-weather': weatherTool } });
// after
createCodeMode({ tools: { 'weather123': weatherTool } }); Defensive patterns
Strategy: validation
Validate before calling
const SAFE_IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
for (const id of Object.keys(tools)) {
if (!SAFE_IDENT.test(sanitizeToolId(id))) {
throw new Error(`Tool id "${id}" is not Code Mode-safe; rename it`);
}
} Type guard
function isCodeModeSafeId(id: string): boolean {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(id) && id.length > 0;
} Try / catch
try {
codeMode = createCodeMode({ tools });
} catch (e) {
if (String(e.message).includes('Invalid Code Mode external identifier')) {
// sanitize/rename offending tool ids programmatically
} else throw e;
} Prevention
- Name tools with leading letters (never digits or punctuation).
- Validate tool ids at registration time, not at Code Mode build time.
- Reject user-supplied tool ids that fail identifier checks.
When it happens
Trigger: A tool id sanitizes (via sanitizeToolId) to a string that is not a legal JS identifier, e.g. starts with a digit ('123-tool' -> '123_tool') or is empty/whitespace after sanitization.
Common situations: Tool names beginning with numbers or composed only of punctuation; dynamically generated tool ids from user input; non-ASCII tool ids stripped to an empty string by sanitization.
Related errors
- Code Mode external identifier collision: tools "${existing}"
- Project name must be 1-214 lowercase characters, start with
- Code Mode tool id collision: "${prior}" and "${toolId}" both
- Invalid ${kind}: ${name}. Must start with a letter or unders
- Invalid skill name "${name}". Names must start with alphanum
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/741fd8bf368dcb95.
Report an issue: GitHub.