{"record":{"id":"354dbf0e45b8e6c4","repo":"ReactiveX/rxjs","slug":"duplicate-output-path-outputpath","errorCode":null,"errorMessage":"Duplicate output path: ${outputPath}","messagePattern":"Duplicate output path: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/migrate/src/node.ts","lineNumber":149,"sourceCode":"  for (const { outputPath, result } of plan.files) {\n    await mkdir(dirname(outputPath), { recursive: true });\n    await writeFile(outputPath, result.code, 'utf8');\n  }\n  return plan.files;\n}\n\nasync function preflightPlanOutputs(plan: MigrationFilePlan, options: ApplyMigrationPlanOptions): Promise<void> {\n  const outputRoot = resolve(plan.outputRoot);\n  const canonicalOutputRoot = await canonicalFutureDirectory(outputRoot, 'outputRoot');\n  const canonicalOutputs = new Set<string>();\n  for (const { sourcePath, outputPath, result } of plan.files) {\n    if (result.status === 'refused') throw new Error(`Migration result was refused for source: ${sourcePath}`);\n    const resolvedOutputPath = resolve(outputPath);\n    if (resolvedOutputPath === outputRoot) throw new Error(`Output path must identify a file below outputRoot: ${outputPath}`);\n    assertContained(outputRoot, resolvedOutputPath, `Output path is outside outputRoot: ${outputPath}`);\n    const canonicalOutputPath = await canonicalFuturePath(resolvedOutputPath);\n    assertContained(canonicalOutputRoot, canonicalOutputPath, `Output path resolves outside outputRoot: ${outputPath}`);\n    if (canonicalOutputs.has(canonicalOutputPath)) throw new Error(`Duplicate output path: ${outputPath}`);\n    canonicalOutputs.add(canonicalOutputPath);\n    await assertWritableOutput(resolvedOutputPath, options.overwrite ?? false);\n  }\n}\n\nasync function assertWritableOutput(outputPath: string, overwrite: boolean): Promise<void> {\n  try {\n    const outputStats = await lstat(outputPath);\n    if (outputStats.isSymbolicLink()) throw new Error(`Refusing to overwrite a symbolic link: ${outputPath}`);\n    if (!outputStats.isFile()) throw new Error(`Output path is not a regular file: ${outputPath}`);\n    if (!overwrite) throw new Error(`Output path already exists; enable overwrite explicitly: ${outputPath}`);\n  } catch (error: unknown) {\n    if (!isMissingPathError(error)) throw error;\n  }\n}\n\nexport async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {\n  if (options.write && !options.outputRoot) {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/migrate/src/node.ts#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use planMigrationFiles to generate plans; it already rejects duplicate outputs at planning time","De-duplicate plan.files by resolved outputPath before calling applyMigrationPlan","Remove symlinked directories in outputRoot that alias multiple relative paths to one real path"],"exampleFix":"// before\nawait applyMigrationPlan({ ...plan, files: [...plan.files, plan.files[0]] });\n\n// after\nconst seen = new Set();\nconst files = plan.files.filter(f => {\n  const key = resolve(f.outputPath);\n  if (seen.has(key)) return false;\n  seen.add(key);\n  return true;\n});\nawait applyMigrationPlan({ ...plan, files });","handlingStrategy":"validation","validationCode":"const seen = new Set<string>();\nfor (const f of plan.files) {\n  const key = resolve(f.outputPath);\n  if (seen.has(key)) throw new Error(`duplicate plan output ${key}`);\n  seen.add(key);\n}\nawait applyMigrationPlan(plan);","typeGuard":"function hasUniquePlanOutputs(plan: { files: { outputPath: string }[] }): boolean {\n  const seen = new Set<string>();\n  return plan.files.every(f => {\n    const key = resolve(f.outputPath);\n    if (seen.has(key)) return false;\n    seen.add(key);\n    return true;\n  });\n}","tryCatchPattern":null,"preventionTips":["Do not merge or concatenate plans","Deduplicate by resolved path before applying","Avoid symlinked directories inside outputRoot"],"tags":["migrate","filesystem","duplicate","plan-apply"],"backgroundTag":"output-path-collision","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}