ReactiveX/rxjs · error · Error
Output path must identify a file below outputRoot: ${outputP
Error message
Output path must identify a file below outputRoot: ${outputPath} What it means
Before writing, preflightPlanOutputs resolves each plan entry's outputPath and verifies it is strictly below outputRoot. If the resolved output path equals outputRoot itself, the migrator would be trying to write a file onto the directory root, so it throws. This protects the destination tree from being clobbered by a degenerate path.
Source
Thrown at packages/migrate/src/node.ts:145
plan: MigrationFilePlan,
options: ApplyMigrationPlanOptions = {}
): Promise<readonly MigratedFile[]> {
await preflightPlanOutputs(plan, options);
for (const { outputPath, result } of plan.files) {
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, result.code, 'utf8');
}
return plan.files;
}
async function preflightPlanOutputs(plan: MigrationFilePlan, options: ApplyMigrationPlanOptions): Promise<void> {
const outputRoot = resolve(plan.outputRoot);
const canonicalOutputRoot = await canonicalFutureDirectory(outputRoot, 'outputRoot');
const canonicalOutputs = new Set<string>();
for (const { sourcePath, outputPath, result } of plan.files) {
if (result.status === 'refused') throw new Error(`Migration result was refused for source: ${sourcePath}`);
const resolvedOutputPath = resolve(outputPath);
if (resolvedOutputPath === outputRoot) throw new Error(`Output path must identify a file below outputRoot: ${outputPath}`);
assertContained(outputRoot, resolvedOutputPath, `Output path is outside outputRoot: ${outputPath}`);
const canonicalOutputPath = await canonicalFuturePath(resolvedOutputPath);
assertContained(canonicalOutputRoot, canonicalOutputPath, `Output path resolves outside outputRoot: ${outputPath}`);
if (canonicalOutputs.has(canonicalOutputPath)) throw new Error(`Duplicate output path: ${outputPath}`);
canonicalOutputs.add(canonicalOutputPath);
await assertWritableOutput(resolvedOutputPath, options.overwrite ?? false);
}
}
async function assertWritableOutput(outputPath: string, overwrite: boolean): Promise<void> {
try {
const outputStats = await lstat(outputPath);
if (outputStats.isSymbolicLink()) throw new Error(`Refusing to overwrite a symbolic link: ${outputPath}`);
if (!outputStats.isFile()) throw new Error(`Output path is not a regular file: ${outputPath}`);
if (!overwrite) throw new Error(`Output path already exists; enable overwrite explicitly: ${outputPath}`);
} catch (error: unknown) {
if (!isMissingPathError(error)) throw error;
}View on GitHub (pinned to 54796b38a5)
Solutions
- Build plans with planMigrationFiles rather than constructing them by hand, so safeOutputPath enforces file-below-root
- Fix the offending entry so outputPath points at a concrete file beneath outputRoot
- If post-processing plan paths, assert each is relative and non-empty before applying
Example fix
// before plan.files[0].outputPath = plan.outputRoot; // after plan.files[0].outputPath = path.join(plan.outputRoot, ' migrated.spec.ts');
Defensive patterns
Strategy: validation
Validate before calling
const root = resolve(plan.outputRoot); const ok = plan.files.every(f => resolve(f.outputPath) !== root); if (ok) await applyMigrationPlan(plan);
Type guard
function hasFileOutputsBelowRoot(plan: { outputRoot: string; files: { outputPath: string }[] }): boolean {
const root = resolve(plan.outputRoot);
return plan.files.every(f => resolve(f.outputPath) !== root);
} Prevention
- Never hand-construct MigrationFilePlan; use planMigrationFiles
- When rewriting plan paths, keep them relative to outputRoot and non-empty
When it happens
Trigger: Calling applyMigrationPlan with a hand-built or mutated MigrationFilePlan whose files[].outputPath resolves exactly to plan.outputRoot.
Common situations: Constructing a plan manually (instead of via planMigrationFiles) with outputPath set to '.' or to the outputRoot string; or code that rewrites plan paths and accidentally collapses one onto the root.
Related errors
- Output path is not a regular file: ${outputPath}
- Output name must be a non-empty relative path: ${outputName
- Output name must identify a file below outputRoot: ${outputN
- Duplicate output path: ${outputLocalName}
- Duplicate output path: ${outputPath}
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/f2fa025a8caaa850.
Report an issue: GitHub.