apple/pkl · error · CliException

Output file conflict: `output.files` entries

Error message

Output file conflict: `output.files` entries `"${previousOutput.pathSpec}"` in module `${previousOutput.moduleUri}` and `"$pathSpec"` in module `$moduleUri` resolve to the same file path `$realPath`.

What it means

Two `output.files` entries (possibly from different modules) resolve to the same real file path, so the CLI aborts instead of letting one silently overwrite the other. writeMultipleFileOutput tracks every written path in `writtenFiles` keyed by the normalized real path and throws when a collision is found.

Solutions

  1. Rename one of the conflicting `output.files` entries so each resolves to a unique path.
  2. If overwrite is intended, change the setup so only one entry targets the file, or move to a single-module output.
  3. Normalize path specs (remove `./`, `..`) to spot accidental duplicates across modules.

Example fix

// module A before
output.files { ["out.json"] }
// module B before
output.files { ["./out.json"] }
// after: module B
output.files { ["out-b.json"] }
Defensive patterns

Strategy: validation

Validate before calling

val seen = mutableSetOf<Path>()
for (spec in allOutputPathSpecs()) {
  val real = outDir.resolveRealPath(spec).realPath
  check(seen.add(real)) { "duplicate output path: $spec" }
}

Try / catch

try {
  pklCli.runEval(args)
} catch (e: CliException) {
  if ("resolve to the same file path" in (e.message ?: "")) dedupeOutputsAndRetry()
  else throw e
}

Prevention

When it happens

Trigger: Two modules declare output paths that normalize identically, e.g. `./out.pkl` and `out.pkl` in different modules, or `a/../out.txt` and `out.txt`, while evaluating with multiple input modules or projects.

Common situations: Multi-module monorepos where several PklProjects emit the same output filename; refactors that add a second module outputting to a shared path; glob-like duplicate path specs after directory reorganization.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/957ec34f0170aff4. Report an issue: GitHub.

Appendix: source

Thrown at pkl-cli/src/main/kotlin/org/pkl/cli/CliEvaluator.kt:251

      for ((moduleUri, outputDir) in outputDirs) {
        if (outputDir.exists() && !outputDir.isDirectory()) {
          throw CliException("Output path `$outputDir` exists and is not a directory.")
        }
        val moduleSource = toModuleSource(moduleUri, inputStream)
        val output = evaluator.evaluateOutputFiles(moduleSource)
        val realOutputDir = if (outputDir.exists()) outputDir.toRealPath() else outputDir

        for ((pathSpec, fileOutput) in output) {
          checkPathSpec(pathSpec)
          val (realPath, resolvedPath) = realOutputDir.resolveRealPath(Path.of(pathSpec))
          if (!realPath.startsWith(realOutputDir)) {
            throw CliException(
              "Output file conflict: `output.files` entry `\"$pathSpec\"` in module `$moduleUri` resolves to file path `$realPath`, which is outside output directory `$realOutputDir`."
            )
          }
          val previousOutput = writtenFiles[realPath]
          if (previousOutput != null) {
            throw CliException(
              "Output file conflict: `output.files` entries `\"${previousOutput.pathSpec}\"` in module `${previousOutput.moduleUri}` and `\"$pathSpec\"` in module `$moduleUri` resolve to the same file path `$realPath`."
            )
          }
          if (realPath.isDirectory()) {
            throw CliException(
              "Output file conflict: `output.files` entry `\"$pathSpec\"` in module `$moduleUri` resolves to file path `$realPath`, which is a directory."
            )
          }
          writtenFiles[realPath] = OutputFile(pathSpec, moduleUri)
          realPath.createParentDirectories()
          realPath.writeBytes(fileOutput.bytes)
          outputStream.writeText(
            IoUtils.relativize(resolvedPath, currentWorkingDir).toString() +
              IoUtils.getLineSeparator()
          )
          outputStream.flush()
        }
      }

View on GitHub (pinned to f3efcbfc9b)