ReactiveX/rxjs · error · Error
${message}
Error message
${message} What it means
Thrown by assertContained when a candidate path resolves to the root itself or escapes outside the given root directory (the relative path from root to candidate is '' or starts with '..' or is absolute). The migrate CLI uses it as a path-traversal guard so outputs and skill installs stay inside the project root. This is a safety invariant, not a filesystem failure.
Source
Thrown at packages/migrate/src/node.ts:187
}
const plan = await planMigrationFiles(options);
return options.write ? applyMigrationPlan(plan, { overwrite: options.overwrite }) : plan.files;
}
function safeOutputPath(outputRoot: string, outputName: string): string {
if (!outputName || outputName === '.' || isAbsolute(outputName)) {
throw new Error(`Output name must be a non-empty relative path: ${outputName || '<empty>'}`);
}
const outputPath = resolve(outputRoot, outputName);
if (outputPath === outputRoot) throw new Error(`Output name must identify a file below outputRoot: ${outputName}`);
assertContained(outputRoot, outputPath, `Output path is outside outputRoot: ${outputName}`);
return outputPath;
}
function assertContained(root: string, candidate: string, message: string): void {
const localPath = relative(root, candidate);
if (localPath === '' || (localPath !== '..' && !localPath.startsWith(`..${sep}`) && !isAbsolute(localPath))) return;
throw new Error(message);
}
async function canonicalDirectory(path: string, label: string): Promise<string> {
const canonicalPath = await realpath(path);
const pathStats = await stat(canonicalPath);
if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);
return canonicalPath;
}
async function canonicalFutureDirectory(path: string, label: string): Promise<string> {
const resolved = await canonicalFuturePath(path);
try {
const pathStats = await stat(path);
if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);
} catch (error: unknown) {
if (!isMissingPathError(error)) throw error;
}
return resolved;View on GitHub (pinned to 54796b38a5)
Solutions
- Change the output/skill target path so it resolves strictly inside the configured root directory
- Remove '..' segments and absolute paths from output configuration
- If a symlink intentionally points outside the root, replace it with a real directory or move the target inside the root
- Pass the intended project root explicitly instead of relying on cwd resolution
Example fix
// before await safeOutputPath(projectRoot, '/var/tmp/out'); // after await safeOutputPath(projectRoot, 'var-tmp-out'); // stays under projectRoot
Defensive patterns
Strategy: validation
Validate before calling
import { relative, isAbsolute } from 'node:path';
const contained = (root: string, c: string) => {
const r = relative(root, c);
return r !== '' && (r === '..' || r.startsWith(`..${sep}`) || isAbsolute(r));
};
if (contained(root, candidate)) throw new TypeError(`Output escapes root: ${candidate}`); Type guard
const isContainedPath = (root: string, candidate: string): boolean => {
const r = relative(root, candidate);
return r === '' || (r !== '..' && !r.startsWith(`..${sep}`) && !isAbsolute(r));
}; Try / catch
try { await safeOutputPath(root, out); } catch (e) { if (e instanceof Error && /outside|escape/i.test(e.message)) { /* reconfigure output path */ } throw e; } Prevention
- Always build output paths with path.resolve(root, relativePortion)
- Reject absolute output paths at the config layer
- Test symlinks in the output chain
When it happens
Trigger: Calling planMigrationFiles, preflightPlanOutputs, or safeOutputPath with an output path like '../outside-dir' or '/etc' that resolves outside the resolved source/output root; also symlinked targets whose canonical path escapes the root.
Common situations: Passing an absolute output directory, using '..' segments in --output, or a symlink inside the project pointing elsewhere so the canonical path leaves the root.
Related errors
- ${message}
- ${label} is not a directory: ${path}
- An output path ancestor is not a directory: ${existing}
- Project root is not a directory: ${projectRoot}
- Canonical Skill root is not a directory: ${canonicalSkillRoo
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/74071effbb954cea.
Report an issue: GitHub.