{"record":{"id":"9e524273c7ea7f27","repo":"ReactiveX/rxjs","slug":"duplicate-output-path-outputlocalname","errorCode":null,"errorMessage":"Duplicate output path: ${outputLocalName}","messagePattern":"Duplicate output path: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/migrate/src/node.ts","lineNumber":104,"sourceCode":"      canonicalSourcePath,\n      localSourcePath,\n      source: await readFile(canonicalSourcePath, 'utf8'),\n    });\n  }\n\n  const files: MigratedFile[] = [];\n  const canonicalOutputs = new Set<string>();\n  for (const plannedSource of sources) {\n    const outputLocalName = outputName({ sourcePath: plannedSource.sourcePath, sourceRoot, mode: outputMode });\n    const outputPath = safeOutputPath(outputRoot, outputLocalName);\n    const canonicalOutputPath = await canonicalFuturePath(outputPath);\n    assertContained(canonicalOutputRoot, canonicalOutputPath, `Output path resolves outside outputRoot: ${outputLocalName}`);\n\n    if (canonicalSources.has(canonicalOutputPath)) {\n      throw new Error(`Output path aliases a source file: ${outputLocalName}`);\n    }\n    if (canonicalOutputs.has(canonicalOutputPath)) {\n      throw new Error(`Duplicate output path: ${outputLocalName}`);\n    }\n    canonicalOutputs.add(canonicalOutputPath);\n\n    const provenance: SourceProvenance = {\n      repository: options.sourceRepository,\n      sha: options.sourceSha,\n      path: plannedSource.localSourcePath,\n    };\n    const result = migrateTestSource(plannedSource.source, {\n      ...options,\n      mode: options.mode,\n      provenance,\n      fileName: plannedSource.localSourcePath,\n    });\n    files.push({ sourcePath: plannedSource.sourcePath, outputPath, result });\n  }\n\n  return { sourceRoot, outputRoot, files };","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/migrate/src/node.ts#L86-L122","documentation":"During planning, the migration computes an output path for each input file and tracks the canonical (realpath-resolved) destination of every output. If two different source files resolve to the same canonical output path, the second one triggers this error, because applying the plan would have one file silently clobber another. This is a planning-time integrity check in planMigrationFiles, so nothing has been written when it throws.","triggerScenarios":"Calling planMigrationFiles (or migrateTestFiles / apiPlan) with multiple files whose outputName function returns the same relative path for different sources, or returns paths that differ only by symlinked directories that canonicalize identically.","commonSituations":"A custom outputName callback that derives names from a basename (two files named foo.spec.ts in different directories), or outputRoot containing symlinks that collapse distinct relative paths onto one real location.","solutions":["Fix the outputName function so each source maps to a unique relative path (e.g. include the source's directory in the name)","Check whether outputRoot or intermediate directories are symlinks collapsing distinct paths, and use a non-aliased outputRoot","If only one file is intended, pass a single file instead of several"],"exampleFix":"// before\noutputName: () => path.basename(sourcePath).replace(/\\.ts$/, '.next.ts'),\n\n// after\noutputName: ({ sourcePath, sourceRoot }) =>\n  path.relative(sourceRoot, sourcePath).replace(/\\.ts$/, '.next.ts'),","handlingStrategy":"validation","validationCode":"const seen = new Set<string>();\nfor (const input of options.files) {\n  const name = outputName({ sourcePath: resolve(sourceRoot, input), sourceRoot, mode: options.mode ?? 'unselected' });\n  const key = resolve(outputRoot, name);\n  if (seen.has(key)) throw new Error(`duplicate output for ${input}`);\n  seen.add(key);\n}","typeGuard":"function isUniqueOutputNames(files: string[], outputName: (ctx: { sourcePath: string }) => string, outputRoot: string): boolean {\n  const seen = new Set<string>();\n  return files.every(f => {\n    const key = resolve(outputRoot, outputName({ sourcePath: f }));\n    if (seen.has(key)) return false;\n    seen.add(key);\n    return true;\n  });\n}","tryCatchPattern":"try {\n  await migrateTestFiles(options);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Duplicate output path')) {\n    // fix outputName to be source-unique, then retry\n  } else throw e;\n}","preventionTips":["Derive output names from the full source-relative path, not just the basename","Keep outputRoot free of symlinks that could canonicalize two names to one path"],"tags":["migrate","filesystem","duplicate","planning"],"backgroundTag":"output-path-collision","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}