mastra-ai/mastra · error · Error
Experiment worker artifacts cannot contain escaping symlinks
Error message
Experiment worker artifacts cannot contain escaping symlinks: ${artifactPath} What it means
During artifact digesting, a symlink whose relative target resolves outside the artifact root (relative(root, resolved) escapes with '..' or lands on an absolute path) is rejected. Such links would make the unpacked artifact read or point to files outside its own tree — a security and reproducibility hazard — so the build aborts naming the offending link.
Source
Thrown at packages/cli/src/commands/experiment/ExperimentBundler.ts:277
const stats = await lstat(fullPath);
if (stats.isDirectory()) {
await visit(fullPath);
} else if (stats.isFile()) {
files.push({
path: artifactPath,
sha256: createHash('sha256')
.update(await readFile(fullPath))
.digest('hex'),
});
} else if (stats.isSymbolicLink()) {
const target = await readlink(fullPath);
if (isAbsolute(target)) {
throw new Error(`Experiment worker artifacts cannot contain absolute symlinks: ${artifactPath}`);
}
const resolvedTarget = resolve(dirname(fullPath), target);
const artifactTarget = relative(root, resolvedTarget);
if (artifactTarget === '..' || artifactTarget.startsWith(`..${sep}`) || isAbsolute(artifactTarget)) {
throw new Error(`Experiment worker artifacts cannot contain escaping symlinks: ${artifactPath}`);
}
files.push({
path: artifactPath,
type: 'symlink',
target,
sha256: createHash('sha256').update(target).digest('hex'),
});
} else {
throw new Error(`Unsupported artifact file type: ${artifactPath}`);
}
}
};
await visit(root);
return files;
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Locate the listed symlink and retarget it to a path inside the artifact (or replace it with a real copy).
- Bundle the linked dependency's contents instead of linking (e.g. rely on the bundler's dynamicPackages/externals handling rather than filesystem links).
- If it comes from a dependency's install script, patch or vendor the dependency.
- Clean the output directory and rebuild to remove stale links.
Example fix
// before ln -s ../../shared/transforms ./transforms // after cp -r ../../shared/transforms ./transforms # real copy inside the artifact
Defensive patterns
Strategy: validation
Validate before calling
import { lstat, readlink, readdir } from 'node:fs/promises';
import { resolve, dirname, relative, isAbsolute, sep, join } from 'node:path';
async function assertNoEscapingSymlinks(root: string, dir = root) {
for (const e of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) await assertNoEscapingSymlinks(root, p);
else if (e.isSymbolicLink()) {
const t = relative(root, resolve(dirname(p), await readlink(p)));
if (t === '..' || t.startsWith(`..${sep}`) || isAbsolute(t)) throw new Error(`escaping symlink: ${p}`);
}
}
} Type guard
null
Try / catch
try {
await buildExperimentWorker({ outputDir });
} catch (e) {
if (String(e?.message).includes('escaping symlinks')) {
console.error('Retarget the listed symlink inside the artifact or copy the files in');
} else throw e;
} Prevention
- Never symlink from the output/source tree to paths outside the project
- Copy shared assets into the tree instead of linking to siblings
- Check monorepo workspace links that resolve outside the package
- Audit node_modules for virtual-store symlinks before packaging
When it happens
Trigger: collectFileDigests finds a symlink in the output directory whose target, resolved against the link's directory, escapes the artifact root, e.g. `../../../etc/something` or `../../shared-lib`.
Common situations: Monorepo setups where packages symlink to sibling workspace packages outside the output; vendored code linking to a shared assets dir; pnpm-style virtual-store links escaping the bundle; copying source trees that contain parent-relative links.
Related errors
- Path is outside the browsable root
- Path is outside the workspace
- Path escapes workspace root (symlink): ${inputPath}
- Experiment worker artifacts cannot contain absolute symlinks
- Cookie password must be at least 32 characters for SSO. Set
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/57b0cedea6563aa1.
Report an issue: GitHub.