ReactiveX/rxjs · error · Error
Output name must identify a file below outputRoot: ${outputN
Error message
Output name must identify a file below outputRoot: ${outputName} What it means
After resolving outputName against outputRoot, safeOutputPath verifies the result is strictly below the root. A name like 'sub/..' (or any name whose segments cancel out to the root itself) resolves back to outputRoot, which is a directory, not a writable output file, so it is rejected. This is distinct from the outside-root case, which assertContained handles next.
Source
Thrown at packages/migrate/src/node.ts:179
} catch (error: unknown) {
if (!isMissingPathError(error)) throw error;
}
}
export async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {
if (options.write && !options.outputRoot) {
throw new Error('outputRoot is required when write is enabled.');
}
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> {View on GitHub (pinned to 54796b38a5)
Solutions
- Ensure outputName ends with a concrete filename (with extension) after any segment manipulation
- Compute the name from path.relative(sourceRoot, sourcePath) so it always retains the basename
- Add a unit assertion that your outputName output never resolves to the outputRoot
Example fix
// before
outputName: ({ sourcePath }) => path.dirname(path.relative(sourceRoot, sourcePath)),
// after
outputName: ({ sourcePath }) => path.relative(sourceRoot, sourcePath).replace(/\.ts$/, '.next.ts'), Defensive patterns
Strategy: validation
Validate before calling
import { resolve } from 'node:path';
function nameIdentifiesFile(outputRoot: string, name: string): boolean {
return resolve(outputRoot, name) !== resolve(outputRoot);
} Type guard
function isFileBelowRoot(outputRoot: string, name: unknown): name is string {
return typeof name === 'string' && name.length > 0 && name !== '.' && resolve(outputRoot, name) !== resolve(outputRoot);
} Prevention
- Always include the source basename (with extension) in generated output names
- Avoid segment-cancelling names like 'foo/..' in outputName logic
When it happens
Trigger: options.outputName returning a path whose resolution equals outputRoot, e.g. 'foo/..' or '.'-equivalent segment chains like 'a/../..'-style collapse when outputRoot is a parent, or the literal name '.' (that specific case is caught one line earlier).
Common situations: A name-deriving callback that strips extensions and segments (e.g. removes everything after the last slash) and ends up with an empty/identity path; naive string manipulation on relative paths.
Related errors
- Output name must be a non-empty relative path: ${outputName
- Output path must identify a file below outputRoot: ${outputP
- Output path is not a regular file: ${outputPath}
- Duplicate output path: ${outputLocalName}
- Duplicate output path: ${outputPath}
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/006281b9caaa64e2.
Report an issue: GitHub.