{{ message }}
Repository navigation
Fix Node.js 26 ERR_INTERNAL_ASSERTION on HTTP/1 responses - #85
Merged
Merged
Conversation
When using a native HTTP/2 server with `allowHTTP1`, HTTP/1 requests were augmented with the express request API by wrapping them with `Object.create()`. Express then assigned the wrapper to `res.req`. Starting with Node.js 26.11.0 (nodejs/node#65802), the response finish handler reads `res.req` and asserts that it is the original request, causing `ERR_INTERNAL_ASSERTION` on every HTTP/1 response. Augment HTTP/1 requests in place instead so the request object identity is preserved. HTTP/2 requests are unchanged. Add a test that checks express handlers receive the request object that node emitted. It fails on all node versions without this fix.
dlongley
approved these changes
Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fix Node.js 26
ERR_INTERNAL_ASSERTIONon HTTP/1 responsesSymptom
On Node.js 26, every HTTP/1 response from a Bedrock app fails with:
This was first seen in
bedrock-kms-httpCI (38–51 failing tests on 26.x, all passing on 22.x/24.x). It reproduces with only@bedrock/server+@bedrock/express, and this repo's own test suite fails on Node 26 too.The problem first appears in Node.js v26.11.0 (v26.10.0 and all 25.x releases are unaffected). It comes from nodejs/node@15184e8212, "http: reduce per-request allocations in the server" (nodejs/node#65802).
Cause
@bedrock/serverserves HTTP/1 throughhttp2.createSecureServer({allowHTTP1: true}). Because the server is native HTTP/2, theexpressHttp2hook augments every request with the express request API, HTTP/1 requests included._http2.augment()returnsObject.create(target, descriptors), a new object that has the real request as its prototype. That wrapper becomesreq.raw, and_customExpressInitthen setsres.req = reqto the wrapper.reqinto each response'sfinishhandler (resOnFinish.bind(undefined, req, ...)), so the value ofres.reqdidn't matter.onResponseFinishhandler that readsthis.req(http: reduce per-request allocations in the server nodejs/node#65802). It then assertsstate.incoming[0] === req. The wrapper is not the original request, so the assertion fails.Instrumenting
res.reqconfirmed it:res.req === original req: false,incoming[0] === original req: true,incoming.length: 1. The replacement happened in_customExpressInit.Fix
For HTTP/1 requests, define the express request descriptors in place on the raw request (
Object.defineProperties) instead of wrapping it, so the request object Node created is the one express sees. Changes:augment()takes a new{inPlace}option.http1Request). The cache filters out properties already present on the first target, and HTTP/1IncomingMessageand HTTP/2Http2ServerRequestobjects don't have the same properties.hostname,ipandipsare still excluded from the descriptors, soexpressifyRequestcan keep assigning them. This is also whyObject.setPrototypeOf(req.raw, app.request)(the approach used for HTTP/1 responses) doesn't work for requests: those properties are getter-only on the express request prototype.Tests
should preserve the node request object. The new/request-identityroute checks that thereqan express handler receives is the object Node emitted for'request', and thatres.req === req. It fails on every Node version without this fix, so the bug is caught even without Node 26 in the matrix.bedrock-kms-http's test suite passes on Node 26 with this change.Follow-up
@bedrock/expressinbedrock-kms-http(and other consumers testing on Node 26).res.reqworked through v26.10.0 and now triggers an internal assertion, which is arguably a regression from http: reduce per-request allocations in the server nodejs/node#65802, though this fix doesn't depend on that.