Summary
Under load, a Hono service on @hono/node-server dies with an unhandled
TypeError: is not iterable
at node_modules/@hono/node-server/dist/index.mjs:595
Line 595 is inside the response's "close" handler:
outgoing.on("close", () => {
const abortController = req[abortControllerKey];
if (abortController) {
if (incoming.errored) {
req[abortControllerKey].abort(incoming.errored.toString());
} else if (!outgoing.writableFinished) { // ← 595
req[abortControllerKey].abort("Client connection prematurely closed.");
}
}
…
});
So it is a property read (outgoing.writableFinished) or the surrounding
symbol-keyed access on the connection-close path. The error is not catchable
by application code — it happens in the adapter's own listener — so the
process exits and, under systemd, restart-loops.
Perry 0.5.1519 from source + the fixes from #9314 and #9319, Linux x86_64,
@hono/node-server 1.19.17, hono 4.13.4.
Reproducing
It is load- or timing-dependent. A single request never triggers it; six
rapid POSTs in a row reliably do:
run 1: NO CHALLENGE ← process already dead
…
$ ss -lntp | grep 3987 ← nothing listening
$ tail /tmp/dbg.log
mb24-api listening on :3987
TypeError: is not iterable
at node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.4/node_modules/@hono/node-server/dist/index.mjs:595
Built with --debug-symbols, which is what produced the frame — without it
the trace is only at <anonymous>.
What I could NOT reduce
A standalone node:http server that mirrors the shape — an AbortController
stored on the request under a Symbol key, read from res.on("close"), with
res.writableFinished and req.errored accessed and abort() called — runs
correctly under Perry:
close: got controller? true type: object
close: writableFinished = true
close: incoming.errored = undefined
close: abort ok
One incidental difference in that run: incoming.errored is undefined under
Perry and null under node. Probably unrelated, but noting it since it is in
the same expression as the failing line.
So something about the real adapter's state — a streamed response, an early
client disconnect, or concurrency — is needed. I can reproduce it on demand
against a real service and am happy to test a patch or add instrumentation on
that host.
Impact
It is an unhandled throw inside a library listener, so a service cannot guard
against it: the process exits mid-request and every in-flight session dies
with it. On a healthcheck that only asserts 2xx, the restart is invisible.
Summary
Under load, a Hono service on
@hono/node-serverdies with an unhandledLine 595 is inside the response's
"close"handler:So it is a property read (
outgoing.writableFinished) or the surroundingsymbol-keyed access on the connection-close path. The error is not catchable
by application code — it happens in the adapter's own listener — so the
process exits and, under systemd, restart-loops.
Perry
0.5.1519from source + the fixes from #9314 and #9319, Linux x86_64,@hono/node-server1.19.17, hono 4.13.4.Reproducing
It is load- or timing-dependent. A single request never triggers it; six
rapid
POSTs in a row reliably do:Built with
--debug-symbols, which is what produced the frame — without itthe trace is only
at <anonymous>.What I could NOT reduce
A standalone
node:httpserver that mirrors the shape — anAbortControllerstored on the request under a
Symbolkey, read fromres.on("close"), withres.writableFinishedandreq.erroredaccessed andabort()called — runscorrectly under Perry:
One incidental difference in that run:
incoming.erroredisundefinedunderPerry and
nullunder node. Probably unrelated, but noting it since it is inthe same expression as the failing line.
So something about the real adapter's state — a streamed response, an early
client disconnect, or concurrency — is needed. I can reproduce it on demand
against a real service and am happy to test a patch or add instrumentation on
that host.
Impact
It is an unhandled throw inside a library listener, so a service cannot guard
against it: the process exits mid-request and every in-flight session dies
with it. On a healthcheck that only asserts 2xx, the restart is invisible.