ReactiveX/rxjs · error · Error

Duplicate output path: ${outputPath}

Error message

Duplicate output path: ${outputPath}

What it means

Before writing, preflightPlanOutputs tracks the canonical (realpath-resolved) destination of every output file in the plan. If two entries canonicalize to the same location, the second write would overwrite the first, so the whole apply is aborted. Nothing is written when this throws because the check runs in the preflight loop before any writeFile.

Source

Thrown at packages/migrate/src/node.ts:149

  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;
  }
}

export async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {
  if (options.write && !options.outputRoot) {

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Use planMigrationFiles to generate plans; it already rejects duplicate outputs at planning time
  2. De-duplicate plan.files by resolved outputPath before calling applyMigrationPlan
  3. Remove symlinked directories in outputRoot that alias multiple relative paths to one real path

Example fix

// before
await applyMigrationPlan({ ...plan, files: [...plan.files, plan.files[0]] });

// after
const seen = new Set();
const files = plan.files.filter(f => {
  const key = resolve(f.outputPath);
  if (seen.has(key)) return false;
  seen.add(key);
  return true;
});
await applyMigrationPlan({ ...plan, files });
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set<string>();
for (const f of plan.files) {
  const key = resolve(f.outputPath);
  if (seen.has(key)) throw new Error(`duplicate plan output ${key}`);
  seen.add(key);
}
await applyMigrationPlan(plan);

Type guard

function hasUniquePlanOutputs(plan: { files: { outputPath: string }[] }): boolean {
  const seen = new Set<string>();
  return plan.files.every(f => {
    const key = resolve(f.outputPath);
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

Prevention

When it happens

Trigger: Calling applyMigrationPlan on a plan (typically hand-built or mutated) where two files[].outputPath entries differ lexically but resolve to the same canonical path via symlinks, or are literally identical.

Common situations: Manually merging or duplicating entries in a MigrationFilePlan; output directories that are symlinks to the same real directory; re-applying a plan that was concatenated with another.

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/354dbf0e45b8e6c4. Report an issue: GitHub.