ReactiveX/rxjs · error · Error
Refusing to overwrite a symbolic link: ${outputPath}
Error message
Refusing to overwrite a symbolic link: ${outputPath} What it means
assertWritableOutput uses lstat, which inspects the path itself rather than its target, and refuses to overwrite when the existing output path is a symbolic link — even with overwrite enabled. Overwriting a symlink would replace the link with a regular file and could damage setups where outputs are deliberately linked into another tree, so the migrator fail-closes.
Source
Thrown at packages/migrate/src/node.ts:158
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;
}
}
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>'}`);View on GitHub (pinned to 54796b38a5)
Solutions
- Manually remove the offending symlink (rm <path>) so the migrator creates a regular file
- Retarget the plan so outputPath points at the real file location instead of the link
- Change your outputRoot to a directory that is not symlink-managed
Example fix
# before applyMigrationPlan(plan) // throws: Refusing to overwrite a symbolic link # after rm packages/migrated/old-link.spec.ts applyMigrationPlan(plan)
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises';
async function noSymlinkOutputs(plan: { files: { outputPath: string }[] }): Promise<boolean> {
for (const f of plan.files) {
try { if ((await lstat(f.outputPath)).isSymbolicLink()) return false; }
catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; }
}
return true;
} Try / catch
try {
await applyMigrationPlan(plan);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Refusing to overwrite a symbolic link')) {
// remove the symlink manually, then retry
} else throw e;
} Prevention
- Keep migration outputs in a directory with no symlinked entries
- Remember overwrite:true does not bypass the symlink check
When it happens
Trigger: Calling applyMigrationPlan (or migrateTestFiles with write:true) when a planned outputPath exists and lstat reports it as a symlink. Passing overwrite:true does NOT bypass this.
Common situations: Output directories managed with symlinks (dotfile-style layouts, monorepo package linking, containers with linked volumes), or a previous partial migration whose outputs were symlinked elsewhere.
Related errors
- Output path is not a regular file: ${outputPath}
- Output path already exists; enable overwrite explicitly: ${o
- Duplicate output path: ${outputLocalName}
- Output path must identify a file below outputRoot: ${outputP
- Duplicate output path: ${outputPath}
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/076a342a15058500.
Report an issue: GitHub.