apple/pkl · error · CliException

Output file conflict: `output.files` entries `"${previousOut

Error message

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

What it means

When writing per-module file outputs, CliCommandRunner tracks real paths already written; this error means two different `output.files` pathSpec entries (from the same or different module outputs) normalize/resolve to the same physical file, so the later entry would silently overwrite the earlier one. The CLI refuses rather than allow ambiguity.

Source

Thrown at pkl-cli/src/main/kotlin/org/pkl/cli/CliCommandRunner.kt:124

   * anywhere in the filesystem. This is intentionally less sandboxed than `pkl eval` and directly
   * targets the capabilities of CLI tools written in general purpose languages. Pkl commands should
   * therefore be treated as untrusted code the way that any other CLI tool would be.
   */
  fun writeMultipleFileOutput(outputFiles: Map<String, FileOutput>) {
    if (outputFiles.isEmpty()) return

    val writtenFiles = mutableMapOf<Path, String>()
    val outputDir = options.normalizedWorkingDir
    if (outputDir.exists() && !outputDir.isDirectory()) {
      throw CliException("Output path `$outputDir` exists and is not a directory.")
    }
    for ((pathSpec, fileOutput) in outputFiles) {
      checkPathSpec(pathSpec)
      val resolvedPath = outputDir.resolve(pathSpec).normalize()
      val realPath = if (resolvedPath.exists()) resolvedPath.toRealPath() else resolvedPath
      val previousOutput = writtenFiles[realPath]
      if (previousOutput != null) {
        throw CliException(
          "Output file conflict: `output.files` entries `\"${previousOutput}\"` and `\"$pathSpec\"` resolve to the same file path `$realPath`."
        )
      }
      if (realPath.isDirectory()) {
        throw CliException(
          "Output file conflict: `output.files` entry `\"$pathSpec\"` resolves to file path `$realPath`, which is a directory."
        )
      }
      writtenFiles[realPath] = pathSpec
      realPath.createParentDirectories()
      realPath.writeBytes(fileOutput.bytes)
      val displayPath =
        if (Path.of(pathSpec).isAbsolute) pathSpec
        else IoUtils.relativize(resolvedPath, currentWorkingDir).toString()
      errStream.writeText(displayPath + IoUtils.getLineSeparator())
      errStream.flush()
    }
  }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Make each `output.files` entry resolve to a unique path — include `%{moduleDir}` or `%{moduleName}` plus distinguishing segments in pathSpecs.
  2. Inspect the resolved realPath in the message to find which two entries collide.
  3. Avoid symlinks or overlapping output directories that make distinct specs share one real path.
  4. Remove duplicate entries in the `output.files` mapping.

Example fix

// before (output.files in Pkl)
["out.json"] = myOutput
["./out.json"] = otherOutput // same real path
// after
["first/out.json"] = myOutput
["second/out.json"] = otherOutput
Defensive patterns

Strategy: validation

Validate before calling

val resolved = pathSpecs.map { outputDir.resolve(it).normalize().toRealPath() }
require(resolved.distinct().size == resolved.size) { "Duplicate resolved output paths: ${resolved.groupingBy { it }.eachCount().filterValues { it > 1 }}" }

Try / catch

try {
  runner.writeMultipleFileOutput(outputFiles)
} catch (e: CliException) {
  if (e.message?.contains("conflict") == true) {
    // dedupe output.files entries and retry
  }
}

Prevention

When it happens

Trigger: Two `output.files` mappings whose pathSpecs resolve to the same path — e.g. `"a.json"` and `"./a.json"`, symlinks collapsing to one real path, or `"%{moduleName}.json"` patterns that produce identical names for modules with the same name in different directories.

Common situations: Placeholder patterns like `%{moduleName}` colliding when two evaluated modules share a basename; symlinked output directories; duplicate pathSpec entries in the Pkl config.

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/eea7f04e1a8693c3. Report an issue: GitHub.