Troubleshooting
Each entry starts from what you see: an error a call rejects with, a code on a warning, or a behavior. Every code named here is listed in Warning and error codes.
send() rejects with MllpTimeoutError
No acknowledgment arrived within ackTimeoutMs (30 seconds by default) of the bytes being written.
- The peer is slow or not answering. Raise
ackTimeoutMsif the peer is known to be slow, and check that it acknowledges at all: some receivers are configured never to send an acknowledgment. - The peer answers out of order, and the client correlates in order. Look for
MLLP_ACK_UNMATCHED_CONTROL_IDwarnings; setcorrelateByControlId: true. - The peer's
MSA-2does not repeat yourMSH-10. WithcorrelateByControlId: true, such an acknowledgment matches nothing, raisesMLLP_ACK_UNMATCHED_CONTROL_ID, and the send times out.
A timeout does not tell you the message was lost: the peer may have committed it. Resend only when
the receiver de-duplicates on MSH-10 and MSH-7. See
Known limitations.
send() rejects with MllpBackpressureError
The client already holds highWaterMark messages (64 by default) in flight and queued, and
onBackpressure is "reject". Your code is producing faster than the peer acknowledges. Slow the
producer, raise highWaterMark, or use onBackpressure: "wait" with an AbortSignal to bound the
wait. See Send messages.
send() rejects with MllpConnectionError
Read its phase and connectionCause:
phase: "send"with no cause: the client is not connected.createClientdoes not connect until you callconnect(), and a client that is reconnecting refuses sends until it is back.connectionCause: "in-flight-orphan"or"fifo-unsafe": the link dropped while the send was pending, on a client that correlates in order. The first may have been committed, the second was never written. See What happens to pending sends when the link drops.
The client stopped reconnecting
Auto-reconnect retries only failures it classifies as transient. It stops, and the client goes to
CLOSED, on:
tls-verify: the certificate failed verification. Fix the trust anchors (tls.ca) or the host name; retrying meets the same certificate.tls-handshake: a TLS protocol failure, such as no shared cipher suite or a client certificate the server rejected.framing-fatal: the peer sent bytes that are not MLLP, often a health check or an HTTP client on the wrong port, or a frame past the size cap.ENOTFOUNDorEACCES: the host name does not resolve, or the port is not permitted.
A retryStrategy of your own that returns null also stops it. isTransientConnectionError(err)
tells you which way a given error is classified.
The server closes a sender's connection
A framing error the server's tolerance does not cover closes that one connection; the server keeps
serving every other one. The error event carries connectionCause: "framing-fatal" and the
MllpFramingError as its cause, with the code and byteOffset.
MLLP_MISSING_LEADING_VT: the sender omits theVT. If you have identified it and trust it, setframing: { allowMissingLeadingVt: true }on the server.MLLP_FRAME_TOO_LARGE: a frame passedmaxFrameSizeBytes. Raise it throughframingif the sender's messages really are that large.- A connection closed after a quiet period:
deadPeerTimeoutMscloses a connection on which nothing arrived for that long. Set it above the longest quiet period of a healthy link, or leave it unset.
See Tolerate a sender's framing quirks.
The sender receives AE where you expected AA
Watch the server's nack event; its reason says why:
handler-rejected: youronMessagehandler threw or rejected. Its error is not logged by the server; log it inside your handler.uncorrelatable-inbound: the message has no readableMSH, an emptyMSH-10, or is a batch or several messages in one frame. A positive acknowledgment there could not be matched by the sender, so it is never sent. Split batches before they reach you, or answer them yourself withautoAckas a function.discarded-bytes: aVTarrived inside a frame and the decoder discarded the bytes before it (MLLP_TRAILING_BYTES). The message that arrived is a fragment.
The sender cannot match your acknowledgments and resends
The sender matches MSA-2 against the MSH-10 it sent, byte for byte. An acknowledgment built
through a parser can change the control ID: @cosyte/mllp/ack-from-hl7 reports this as
MLLP_ACK_CONTROL_ID_NOT_VERBATIM on a Buffer inbound. Build with buildRawAck, which copies the
bytes, or use the server's autoAck: "AA", which uses it. See
Build ACKs with @cosyte/hl7.
listen() rejects
- With
MllpConnectionErrornaming a wildcard host: binding0.0.0.0,::or another wildcard needsallowWildcardBind: trueon the server. Prefer binding the one interface your senders reach. - With
MllpConnectionErroron a server already listening:listen()runs one at a time. Callclose()before listening again. - With
MllpTlsConfigurationError: read itscode.MLLP_TLS_CIPHER_OPTION_CONFLICTmeans bothatnaTransportSecurityandciphersare set;MLLP_TLS_CIPHER_LIST_REJECTEDmeans no suite inciphersis available;MLLP_TLS_DH_PARAMETERS_REJECTEDmeansdhParametersis unusable. See Secure a link with TLS.
ack-from-hl7 throws MllpPeerMissingError
The subpath needs the optional peer @cosyte/hl7, and it is not installed. Run
npm install @cosyte/hl7. See Installation.
An enhanced-mode send fails with MllpApplicationAckError
The peer sent the accept acknowledgment (CA) and no application acknowledgment followed within
applicationAckTimeoutMs, or the link dropped while waiting (its reason says which). The peer
holds the message, so do not resend it blindly. Our own server sends one acknowledgment per message,
so pointing this client at it with both MSH-15 and MSH-16 asking for acknowledgments always
ends here. See
ACKs and the commit contract.
A securityWarning appears in your logs
MLLP_TLS_VERIFY_DISABLED: a client runs withtls.allowUnverified, so it does not check the server's certificate. Remove the option outside a test.MLLP_BIND_ALL_INTERFACES: a server bound a wildcard host. Confirm that was intended.