apple/pkl · error · ProtocolException
Unexpected end of input; 0 message bytes
Error message
Unexpected end of input; 0 message bytes
What it means
NativeTransport.sendMessage decodes a message received from the native Pkl evaluator over the MessagePack IPC transport. The library throws this ProtocolException when the native side hands over a message whose declared length is 0, meaning there are no message bytes to decode at all. This signals a broken or prematurely closed protocol exchange between the JVM and the native evaluator rather than a decodable (even malformed) message.
Source
Thrown at libpkl/src/main/java/org/pkl/libpkl/NativeTransport.java:64
protected void doSend(Message message) throws ProtocolException {
try (var os = new ByteArrayOutputStream();
var packer = MessagePack.newDefaultPacker(os)) {
var encoder = new ServerMessagePackEncoder(packer);
encoder.encode(message);
sendMessageToNative.accept(os.toByteArray());
} catch (IOException e) {
// impossible; no IO happens during packing
throw PklBugException.unreachableCode();
} catch (ProtocolException e) {
// should never happen; messages coming from Pkl should always be well-formed.
log("Unexpected protocol exception: " + e);
throw e;
}
}
public void sendMessage(int length, CCharPointer ptr) throws ProtocolException {
if (length == 0) {
throw new ProtocolException("Unexpected end of input; 0 message bytes");
}
try (var is = new NativeInputStream(length, ptr);
var unpacker = MessagePack.newDefaultUnpacker(is)) {
var message = new ServerMessagePackDecoder(unpacker).decode();
// guaranteed by `length == 0` check above
assert message != null;
accept(message);
} catch (IOException e) {
// impossible; no IO happens during unpacking
throw PklBugException.unreachableCode();
}
}
}
View on GitHub (pinned to f3efcbfc9b)
Solutions
- Check that the native Pkl evaluator process is still alive and inspect its stderr/stdout logs for a crash just before this error.
- Verify the libpkl native library version matches the org.pkl.libpkl Java bindings version.
- Ensure no code closes or cancels the evaluator concurrently with pending requests; serialize close after all messages are sent.
- Enable verbose transport/protocol logging to capture the last successful frame before the empty one.
- Retry the evaluation with a fresh Evaluator instance if the native side died.
Example fix
// before: sending a message with a null/empty payload buffer
transport.sendMessage(0, null);
// after: guard and only send when a real payload exists
if (payloadBytes == 0 || ptr == null) {
throw new IllegalStateException("no encoded message to send");
}
transport.sendMessage(payloadBytes, ptr); Defensive patterns
Strategy: try-catch
Validate before calling
// before consuming the transport, verify the evaluator is alive
if (!transport.isOpen() || evaluatorHandle == null) {
throw new IllegalStateException("native evaluator not started");
}
if (length <= 0) {
log.error("native peer sent empty frame; evaluator likely crashed");
} Type guard
static boolean hasPayload(int length, CCharPointer ptr) {
return length > 0 && ptr != null;
} Try / catch
try {
transport.sendMessage(length, ptr);
} catch (ProtocolException e) {
log.error("broken native transport: {}", e.getMessage(), e);
evaluator.close(); // native side is unusable; rebuild the evaluator
throw new EvaluationUnavailableException(e);
} Prevention
- Pin the libpkl native library version to match the Java bindings
- Monitor the native evaluator process and its stderr for early crashes
- Never close/cancel the evaluator while requests are in flight
- Log every protocol frame during development to spot empty frames early
When it happens
Trigger: Calling sendMessage (or the underlying evaluator request path it serves) when the native peer passes length==0 into the callback — i.e. the native evaluator produced an empty frame, was shut down mid-conversation, or the transport was misinitialized.
Common situations: The Pkl native binary crashed or exited while the Java evaluator was still exchanging messages; a version mismatch between libpkl native library and the Java bindings; evaluator close/cancel racing an in-flight request.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unexpected incoming one-way message: ${msg}
- Unexpected incoming request message: ${msg}
- Failed to create an evaluator. $e
- malformedMessageHeaderLength
- unhandledMessageCode
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/7ec575437cbf0579.
Report an issue: GitHub.