apple/pkl · error · ExternalReaderProcessException
externalReaderAlreadyTerminated
externalReaderAlreadyTerminated
Error message
externalReaderAlreadyTerminated
What it means
When the external reader process is not closed but the underlying subprocess has already died, getTransport throws ExternalReaderProcessException with code 'externalReaderAlreadyTerminated'. The transport only exists once the process is alive and started, so a dead child means the reader can no longer serve requests.
Source
Thrown at pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java:93
public ExternalModuleResolver getModuleResolver(long evaluatorId)
throws ExternalReaderProcessException {
return ExternalModuleResolver.of(getTransport(), evaluatorId);
}
@Override
public ExternalResourceResolver getResourceResolver(long evaluatorId)
throws ExternalReaderProcessException {
return ExternalResourceResolver.of(getTransport(), evaluatorId);
}
private MessageTransport getTransport() throws ExternalReaderProcessException {
synchronized (lock) {
if (closed) {
throw new IllegalStateException("External reader process has already been closed.");
}
if (process != null) {
if (!process.isAlive()) {
throw new ExternalReaderProcessException(
ErrorMessages.create("externalReaderAlreadyTerminated"));
}
assert transport != null;
return transport;
}
// This relies on Java/OS behavior around PATH resolution, absolute/relative paths, etc.
var command = new ArrayList<String>();
command.add(spec.executable());
if (spec.arguments() != null) {
command.addAll(spec.arguments());
}
var builder = new ProcessBuilder(command);
var workingDir = spec.workingDir();
if (workingDir != null) {
builder.directory(new File(workingDir));View on GitHub (pinned to f3efcbfc9b)
Solutions
- Check the reader process's stderr/exit code to find why it died and fix the root cause
- Verify the external reader binary/launcher is present, executable, and compatible
- Increase memory limits or fix signals that kill the subprocess
- Recreate the external reader process and retry the evaluation once the crash cause is fixed
Example fix
// ensure reader is alive before resolving
if (!processHandle.isAlive()) {
throw new IllegalStateException("reader crashed; check stderr logs before retrying");
}
var resolvers = ExternalResourceResolver.of(reader.getTransport(), evaluatorId); Defensive patterns
Strategy: try-catch
Validate before calling
// before using the reader, confirm the child process is alive
if (readerProcessHandle != null && !readerProcessHandle.isAlive()) {
throw new IllegalStateException("External reader died; check its stderr and exit code");
} Try / catch
try {
var transport = reader.getTransport(); // via resolver accessors
} catch (ExternalReaderProcessException e) {
if (e.getMessage().contains("externalReaderAlreadyTerminated")) {
// inspect reader stderr/exit code, fix crash cause, then recreate the reader
}
} Prevention
- Capture and log the reader process's stderr to diagnose crashes
- Set adequate memory limits so the child is not OOM-killed
- Verify the reader binary version matches your pkl-core version
- Wrap long evaluations with monitoring that detects early child-process death
When it happens
Trigger: The external reader subprocess crashed or was killed (OOM, signal, non-zero exit) and then the evaluator calls getTransport via getModuleResolver/getResourceResolver or spec accessors.
Common situations: Reader binary path wrong or crashing on startup, system OOM-killer terminating the child, timeout/kill of a hung reader, container shutting down the process.
Related errors
- External reader process has already been closed.
- Unexpected incoming one-way message: ${msg}
- Unexpected incoming request message: ${msg}
- externalReaderFailure
- externalReaderFailure
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/daa586f27a796278.
Report an issue: GitHub.