What versions & operating system are you using?
System:
OS: Linux 7.0 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
CPU: (32) x64 AMD Ryzen 9 9950X 16-Core Processor
Memory: 16.45 GB / 30.45 GB
Container: Yes
Shell: 5.9 - /usr/bin/zsh
Binaries:
Node: 22.14.0
npm: 10.9.2
pnpm: 11.24.0
npmPackages:
miniflare: 4.20260730.0 => 4.20260730.0
Please provide a link to a minimal reproduction
https://github.com/Pduhard/miniflare-hyperdrive-crash-repro
Describe the Bug
The local Hyperdrive proxy pipes the workerd client socket to the database socket. Only the database side gets an error listener.
In packages/miniflare/src/plugins/hyperdrive/hyperdrive-proxy.ts on main:
createPlainTCPConnection attaches dbSocket.on("error", ...)
setupTLSConnection attaches tlsSocket.on("error", ...)
clientSocket gets nothing, at any of the four places it is piped: #handleConnection, handlePostgresTlsConnection, handleMySQLTlsConnection, setupTLSConnection. It also gets nothing before those pipes exist: on the TLS paths, #handleConnection writes to the database, reads the reply and runs a TLS handshake first, and a client that resets during that window crashes the process the same way
pipe() does not cover this. It attaches an onerror handler to the destination, but that handler removes itself and re-emits once no other listener is left. So when the client socket errors after the pipes are set up, nothing is listening, and the whole Node process exits. That takes down wrangler dev, getPlatformProxy(), or a vitest run.
Steps to reproduce
git clone https://github.com/Pduhard/miniflare-hyperdrive-crash-repro
cd miniflare-hyperdrive-crash-repro
npm install
npm run repro:half-close
npm run repro:reset
repro:half-close runs a TCP server that speaks just enough of the Postgres protocol to negotiate TLS, then streams data. A Worker connects through a Hyperdrive binding and half-closes its socket while that data is still arriving. Nothing reaches into Miniflare internals.
repro:reset covers the other window: it talks to the proxy port directly and resets the connection while the proxy is still negotiating TLS with the database, before any pipe exists.
Expected: the connection is torn down and Miniflare keeps running.
Observed: the process exits 1 on an uncaught error.
Which configurations reach this code
Since #13390 the proxy is skipped when sslmode=disable, so that mode no longer reaches it. require, prefer, verify-ca and verify-full still do, which is the ordinary setup for developing locally against a hosted Postgres that requires TLS.
| version |
sslmode=disable |
sslmode=require |
4.20260424.0 |
exits 1 |
exits 1 |
4.20260730.0 |
20 of 20 attempts survive |
exits 1 |
The same code is on main today.
Please provide any relevant error logs
node:events:496
throw er; // Unhandled 'error' event
^
Error: write EPIPE
at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:87:19)
Emitted 'error' event on Socket instance at:
at Socket.onerror (node:internal/streams/readable:1028:14)
at Socket.emit (node:events:518:28)
at emitErrorNT (node:internal/streams/destroy:170:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
errno: -32,
code: 'EPIPE',
syscall: 'write'
}
Socket.onerror at readable.js:1028 is the listener pipe() installed on the destination, re-emitting because nothing else was listening. Depending on whether the write lands just before or just after the peer's FIN, the same failure also shows up as Error: This socket has been ended by the other party at Socket.writeAfterFIN.
PR: #15769. It attaches the listener as soon as the connection is accepted and at each pipe site, with a regression test for each of the two windows in packages/miniflare/test/plugins/hyperdrive/proxy.spec.ts. Both tests fail on main.
What versions & operating system are you using?
Please provide a link to a minimal reproduction
https://github.com/Pduhard/miniflare-hyperdrive-crash-repro
Describe the Bug
The local Hyperdrive proxy pipes the workerd client socket to the database socket. Only the database side gets an
errorlistener.In
packages/miniflare/src/plugins/hyperdrive/hyperdrive-proxy.tsonmain:createPlainTCPConnectionattachesdbSocket.on("error", ...)setupTLSConnectionattachestlsSocket.on("error", ...)clientSocketgets nothing, at any of the four places it is piped:#handleConnection,handlePostgresTlsConnection,handleMySQLTlsConnection,setupTLSConnection. It also gets nothing before those pipes exist: on the TLS paths,#handleConnectionwrites to the database, reads the reply and runs a TLS handshake first, and a client that resets during that window crashes the process the same waypipe()does not cover this. It attaches anonerrorhandler to the destination, but that handler removes itself and re-emits once no other listener is left. So when the client socket errors after the pipes are set up, nothing is listening, and the whole Node process exits. That takes downwrangler dev,getPlatformProxy(), or a vitest run.Steps to reproduce
git clone https://github.com/Pduhard/miniflare-hyperdrive-crash-repro cd miniflare-hyperdrive-crash-repro npm install npm run repro:half-close npm run repro:resetrepro:half-closeruns a TCP server that speaks just enough of the Postgres protocol to negotiate TLS, then streams data. A Worker connects through a Hyperdrive binding and half-closes its socket while that data is still arriving. Nothing reaches into Miniflare internals.repro:resetcovers the other window: it talks to the proxy port directly and resets the connection while the proxy is still negotiating TLS with the database, before any pipe exists.Expected: the connection is torn down and Miniflare keeps running.
Observed: the process exits 1 on an uncaught error.
Which configurations reach this code
Since #13390 the proxy is skipped when
sslmode=disable, so that mode no longer reaches it.require,prefer,verify-caandverify-fullstill do, which is the ordinary setup for developing locally against a hosted Postgres that requires TLS.sslmode=disablesslmode=require4.20260424.04.20260730.0The same code is on
maintoday.Please provide any relevant error logs
Socket.onerroratreadable.js:1028is the listenerpipe()installed on the destination, re-emitting because nothing else was listening. Depending on whether the write lands just before or just after the peer's FIN, the same failure also shows up asError: This socket has been ended by the other partyatSocket.writeAfterFIN.PR: #15769. It attaches the listener as soon as the connection is accepted and at each pipe site, with a regression test for each of the two windows in
packages/miniflare/test/plugins/hyperdrive/proxy.spec.ts. Both tests fail onmain.