{"record":{"id":"eea7f04e1a8693c3","repo":"apple/pkl","slug":"output-file-conflict-output-files-entries-p","errorCode":null,"errorMessage":"Output file conflict: `output.files` entries `\"${previousOutput}\"` and `\"$pathSpec\"` resolve to the same file path `$realPath`.","messagePattern":"Output file conflict: `output\\.files` entries `\"(.+?)\"` and `\"\\$pathSpec\"` resolve to the same file path `\\$realPath`\\.","errorType":"exception","errorClass":"CliException","httpStatus":null,"severity":"error","filePath":"pkl-cli/src/main/kotlin/org/pkl/cli/CliCommandRunner.kt","lineNumber":124,"sourceCode":"   * anywhere in the filesystem. This is intentionally less sandboxed than `pkl eval` and directly\n   * targets the capabilities of CLI tools written in general purpose languages. Pkl commands should\n   * therefore be treated as untrusted code the way that any other CLI tool would be.\n   */\n  fun writeMultipleFileOutput(outputFiles: Map<String, FileOutput>) {\n    if (outputFiles.isEmpty()) return\n\n    val writtenFiles = mutableMapOf<Path, String>()\n    val outputDir = options.normalizedWorkingDir\n    if (outputDir.exists() && !outputDir.isDirectory()) {\n      throw CliException(\"Output path `$outputDir` exists and is not a directory.\")\n    }\n    for ((pathSpec, fileOutput) in outputFiles) {\n      checkPathSpec(pathSpec)\n      val resolvedPath = outputDir.resolve(pathSpec).normalize()\n      val realPath = if (resolvedPath.exists()) resolvedPath.toRealPath() else resolvedPath\n      val previousOutput = writtenFiles[realPath]\n      if (previousOutput != null) {\n        throw CliException(\n          \"Output file conflict: `output.files` entries `\\\"${previousOutput}\\\"` and `\\\"$pathSpec\\\"` resolve to the same file path `$realPath`.\"\n        )\n      }\n      if (realPath.isDirectory()) {\n        throw CliException(\n          \"Output file conflict: `output.files` entry `\\\"$pathSpec\\\"` resolves to file path `$realPath`, which is a directory.\"\n        )\n      }\n      writtenFiles[realPath] = pathSpec\n      realPath.createParentDirectories()\n      realPath.writeBytes(fileOutput.bytes)\n      val displayPath =\n        if (Path.of(pathSpec).isAbsolute) pathSpec\n        else IoUtils.relativize(resolvedPath, currentWorkingDir).toString()\n      errStream.writeText(displayPath + IoUtils.getLineSeparator())\n      errStream.flush()\n    }\n  }","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-cli/src/main/kotlin/org/pkl/cli/CliCommandRunner.kt#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"Placeholder patterns like `%{moduleName}` colliding when two evaluated modules share a basename; symlinked output directories; duplicate pathSpec entries in the Pkl config.","solutions":["Make each `output.files` entry resolve to a unique path — include `%{moduleDir}` or `%{moduleName}` plus distinguishing segments in pathSpecs.","Inspect the resolved realPath in the message to find which two entries collide.","Avoid symlinks or overlapping output directories that make distinct specs share one real path.","Remove duplicate entries in the `output.files` mapping."],"exampleFix":"// before (output.files in Pkl)\n[\"out.json\"] = myOutput\n[\"./out.json\"] = otherOutput // same real path\n// after\n[\"first/out.json\"] = myOutput\n[\"second/out.json\"] = otherOutput","handlingStrategy":"validation","validationCode":"val resolved = pathSpecs.map { outputDir.resolve(it).normalize().toRealPath() }\nrequire(resolved.distinct().size == resolved.size) { \"Duplicate resolved output paths: ${resolved.groupingBy { it }.eachCount().filterValues { it > 1 }}\" }","typeGuard":null,"tryCatchPattern":"try {\n  runner.writeMultipleFileOutput(outputFiles)\n} catch (e: CliException) {\n  if (e.message?.contains(\"conflict\") == true) {\n    // dedupe output.files entries and retry\n  }\n}","preventionTips":["Make `output.files` pathSpecs unique, including %{moduleDir} when multiple modules share basenames.","Avoid symlinked output directories that collapse distinct paths.","Review output.files mappings for duplicate or equivalent entries."],"tags":["cli","output-conflict","filesystem"],"backgroundTag":"file-already-exists","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}