apple/pkl · error · PklException
Cannot substitute output path placeholder `%{moduleDir}` bec
Error message
Cannot substitute output path placeholder `%{moduleDir}` because module `$uri` does not have a file system path. What it means
When resolving per-module output file paths, the `%{moduleDir}` placeholder can only be substituted for modules that have a filesystem path (e.g. file:// modules). If a module came from a non-filesystem source (HTTP, classpath, virtual/repl module) and its pathStr still contains `%{moduleDir}` after substitution, CliEvaluator throws this error.
Source
Thrown at pkl-cli/src/main/kotlin/org/pkl/cli/CliEvaluator.kt:135
return options.base.normalizedSourceModules.associateWith { uri ->
val moduleDir: String? =
IoUtils.toPath(uri)?.let {
IoUtils.relativize(it.parent, workingDir).toString().ifEmpty { "." }
}
val moduleKey =
try {
moduleResolver.resolve(uri)
} catch (e: VmException) {
throw e.toPklException(stackFrameTransformer, options.base.color?.hasColor() ?: false)
}
val substituted =
pathStr
.replace("%{moduleName}", IoUtils.inferModuleName(moduleKey))
.replace("%{outputFormat}", options.outputFormat ?: "%{outputFormat}")
.replace("%{moduleDir}", moduleDir ?: "%{moduleDir}")
if (substituted.contains("%{moduleDir}")) {
throw PklException(
"Cannot substitute output path placeholder `%{moduleDir}` " +
"because module `$uri` does not have a file system path."
)
}
val absolutePath = workingDir.resolve(substituted).normalize()
absolutePath
}
}
private fun Evaluator.evalOutput(moduleSource: ModuleSource): ByteArray {
if (options.expression == null) {
return evaluateOutputBytes(moduleSource)
}
return evaluateExpressionString(moduleSource, options.expression)
.toByteArray(StandardCharsets.UTF_8)
}
/** Renders each module's `output.bytes`, writing it to the specified output file. */View on GitHub (pinned to f3efcbfc9b)
Solutions
- Remove `%{moduleDir}` from the output pathSpec or replace it with a fixed directory.
- Evaluate only local file-based modules when output paths depend on %{moduleDir}.
- Materialize remote modules to disk first, then evaluate them as file modules.
- Use `%{moduleName}` instead, which works for modules without a filesystem path.
Example fix
// before
["%{moduleDir}/out.json"] = output // module has no file path
// after
["out/%{moduleName}.json"] = output Defensive patterns
Strategy: validation
Validate before calling
// Ensure %{moduleDir} is only used for file-based modules
if (pathStr.contains("%{moduleDir}") && !moduleUri.scheme.startsWith("file")) {
throw CliException("%{moduleDir} unsupported for non-file module $moduleUri")
} Try / catch
try {
ev.writeMultipleFileOutput(...)
} catch (e: PklException) {
if (e.message?.contains("%{moduleDir}") == true) {
// switch pathSpec to %{moduleName} and retry
}
} Prevention
- Never use %{moduleDir} in output paths when evaluating HTTP/classpath/stdin modules.
- Prefer %{moduleName} for portable output path patterns.
- Materialize remote modules to disk before filesystem-dependent output paths.
When it happens
Trigger: Using `%{moduleDir}` in a multiple-file-output path option while evaluating modules loaded from jar/classpath, HTTP URLs, or stdin (no moduleDir), e.g. `pkl eval --multiple-file-output-path ... https://.../mod.pkl` with pathSpec "%{moduleDir}/out.json".
Common situations: Evaluating remote modules or modules from stdin while an output.files mapping references %{moduleDir}; reusing a config designed for local files against remote dependencies.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Output path `$outputDir` exists and is not a directory.
- Output file `$outputFile` is a directory. Did you mean `--mu
- Output path `$outputDir` exists and is not a directory.
- +e.getMessage()
- Output file conflict: `output.files` entries `"${previousOut
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/17d3ddfb9f56a3a1.
Report an issue: GitHub.