Umbrella for the remaining smaller findings from the pre-stable validation pass (all verified against main @ 75ac47d). Larger items are tracked separately: #1477 (response preparation), #1478 (SSE disconnect), #1479 (non-Error throws), #1481 (!val.ok header drop). The double-readBody Node hang is being addressed upstream in srvx.
Response handling
Exports / types drift
Deprecated compat (src/_deprecated.ts)
Docs-level
Umbrella for the remaining smaller findings from the pre-stable validation pass (all verified against
main@ 75ac47d). Larger items are tracked separately: #1477 (response preparation), #1478 (SSE disconnect), #1479 (non-Error throws), #1481 (!val.okheader drop). The double-readBodyNode hang is being addressed upstream in srvx.Response handling
src/response.ts:129-131: only the fast path (:120) and the immutable-headers catch path (:134) strip HEAD bodies. When prepared headers merge in place into a mutable 2xxResponse, the body is returned intact —app.fetch/web-mode/service-worker consumers see a HEAD response with a body (Node strips it at the socket), contradicting the "keep self-consistent" intent at:119.we can't alter node.js responses in realitykHandledignoresevent.resstate —src/response.ts:62-63: returns a bareFastResponse(null)beforepreparedResis read orkEventRescleared. A handler that setsevent.res.status = 201+ headers then returnskHandledproduces a 200 with no headers, andonResponsehooks observe a synthetic 200 that doesn't match what was actually sent.onResponsehook fails the request raw —src/response.ts:30:Promise.resolve(onResponse(...)).then(() => response)has no rejection handling, so a hook that throws turns the request into an unhandled rejection andonErroris never invoked. If intentional (avoiding hook recursion), it's undocumented.Exports / types drift
kNotFound/kHandlednot exported fromsrc/index.tsthey're part of the documented handler return contract (AGENTS.md), but users can only reach them viafix the agents.md docsSymbol.for("h3.notFound"). Either export them or.H3Coretype is missing"~dispatch"and the static"~h3"brand —src/types/h3.ts:102gained"~composed"in perf(middleware): precompose middleware chains #1475 but not"~dispatch"(src/h3.ts:50), and the exportedH3is cast to the declared type, so TS consumers (e.g. nitro) can't see or invalidate the dispatch cache the wayuse()/mount()do. The static"~h3"brand (src/h3.ts:42, used bysrc/handler.tsfor instance detection) is also absent from the declared class.event.context.clientAddresscan be silently ignored —src/types/context.ts:27declares it, but the only reader isgetRequestIPatsrc/utils/request.ts:542, which readsevent.req.context?.clientAddress— a different object fromevent.contextwhen a context is passed explicitly orreq.contextis unset (H3Event ctor falls back to a fresh object,src/event.ts:83).StaticAssetMeta.pathis typed but never read byserveStatic—src/utils/static.ts:13.Deprecated compat (
src/_deprecated.ts)appendResponseHeadersparam typedstringbut iterated as a record —:245doesObject.entries(headers)over aheaders: stringparam; should beRecord<string, string>(v1 signature). TheappendHeadersalias at:252inherits the wrong type.sendRedirectrequirescode—:133: v1 defaulted to 302 (and the delegatedredirect()hasstatus = 302), socodeshould be optional.Docs-level
bodyLimit/assertBodySizebuffers beforenext()—src/utils/body.ts:224-234drains areq.clone()(up to the limit) before the handler runs, so streaming handlers behindbodyLimit()can't start until the whole body has arrived and is buffered. Enforcement itself is correct (no chunked-encoding bypass;content-length+transfer-encodingrejected with 400) — but the latency/memory behavior deserves a doc note, or eventually a streaming enforcement mode.serve()" — every platform entry exportsserve; onlytoNodeHandleris node-specific.