{"record":{"id":"dcf8e4d5da340415","repo":"apple/pkl","slug":"unexpected-incoming-request-message-msg","errorCode":null,"errorMessage":"Unexpected incoming request message: ${msg}","messagePattern":"Unexpected incoming request message: (.+?)","errorType":"exception","errorClass":"ProtocolException","httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java","lineNumber":149,"sourceCode":"      rxThread.start();\n\n      return transport;\n    }\n  }\n\n  /**\n   * Runs the underlying message transport so it can receive responses from the child process.\n   *\n   * <p>Blocks until the underlying transport is closed.\n   */\n  private void runTransport(MessageTransport transport) {\n    try {\n      transport.start(\n          (msg) -> {\n            throw new ProtocolException(\"Unexpected incoming one-way message: \" + msg);\n          },\n          (msg) -> {\n            throw new ProtocolException(\"Unexpected incoming request message: \" + msg);\n          });\n    } catch (ProtocolException | IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Override\n  public void close() {\n    synchronized (lock) {\n      if (closed) return;\n      closed = true;\n\n      try {\n        if (transport != null && process != null && process.isAlive()) {\n          transport.send(new CloseExternalProcess());\n          process.waitFor(CLOSE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);\n        }\n      } catch (Exception ignored) {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java#L131-L167","documentation":"runTransport also registers a handler for incoming request messages from the reader; the host never expects to serve requests to the reader, so any incoming request is a protocol violation and throws ProtocolException wrapped in a RuntimeException in the rxThread. This protects the host from readers that misuse the request channel.","triggerScenarios":"The external reader process sends a request-type message to the host — e.g. a buggy or newer reader expecting bidirectional requests, or stream corruption making a reply parse as a request.","commonSituations":"Protocol version mismatch between pkl-core and the reader binary, custom reader implementations issuing requests, desynchronized framing after an error.","solutions":["Align protocol versions between pkl-core and the external reader binary","Fix the reader implementation to send only one-way messages/responses, never requests","Log the offending message to identify which request type was sent","Capture a transport trace to check for framing desynchronization"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  reader.use { ... }\n} catch (RuntimeException e) {\n  if (e.getCause() instanceof ProtocolException pe && pe.getMessage().startsWith(\"Unexpected incoming request message\")) {\n    // reader sent a request; align protocol versions or fix the reader\n  }\n}","preventionTips":["Use only spec-conformant readers: they must never send request messages to the host","Version-check the reader binary at startup before evaluation","Record a transport trace when developing a custom reader","Pin reader versions in deployment to avoid silent protocol drift"],"tags":["pkl","protocol","reader","ipc"],"backgroundTag":"protocol-violation","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"}