Repository navigation
fix(router): inspecting a handler ctx no longer throws on Node (#1000) - #1016
Merged
Merged
Conversation
console.log(ctx) and util.inspect(ctx.res) throw on Node because the response-header guard proxies an empty Headers. undici's inspect hook then reads #headersList off that proxy. The proxy target now owns an inspect hook that formats the real stub headers. Closes #1000.
DOM Headers has forEach and not entries, so build:types failed on the fallback the Node inspect hook never takes.
commit: |
This was referenced Oct 3, 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.
Closes #1000.
Problem
On the Node dev server,
console.log(ctx)in a handler, andutil.inspect(ctx.res)orutil.inspect(ctx.res.headers), throw:The response-header guard proxies a fresh empty
Headersand forwards operations to the real stub headers. Node'sutil.inspectunwraps that proxy and runs undici'sHeadersinspect hook with the proxy asthis, which reads#headersListand throws. workerd does not throw, because itsHeadershas no Node inspect hook. The guard still has to reject header writes from a latched cache scope.Before / after
Before, on the Node dev server, preview, and Vercel output, that log throws and the request fails. After, it prints the stub response, including headers written on it. A
ctx.res.headers.setinside a latched cache scope still throws.End-to-end consumer example
A handler that logs the context while setting a header the page depends on:
Before, the
console.logthrew on Node before the header was useful for debugging. After, it printsready. The sameHeadersobject still refuses a write from insidecache()or a"use cache"body.Semantics
console.log(ctx),util.inspect(ctx.res),util.inspect(ctx.res.headers)on Node#headersListctx.res.headers.set/append/deleteoutside a latched cache scopectx.header(),setCookie(),deleteCookie()Code-change summary
The inspect hook is an own property of the proxy target in
packages/rangojs-router/src/server/request-context.ts.util.inspectdoes not consult thegettrap, so a trap-only handler still throws. The hook readsrawStubHeaderson each call, becausesetStatusreplaces thatHeaders. It calls undici's own hook when present, otherwise the header entries.set/append/deletestill consult the write guard.The test builds the handler context the same way
cached-header-guard.test.tsalready does (createRequestContext+runWithRequestContext+createHandlerContext).Test plan
packages/rangojs-router/src/rsc/__tests__/cached-header-guard.test.ts—util.inspect of a handler ctx, ctx.res, and ctx.res.headers shows the live stub headers (#1000). Red first: the new test threw#headersListbefore the hook. Green: 28 passed in that file.packages/rangojs-router/src/server/__tests__/request-context.test.ts— 41 existing tests still passed alongside the guard file (69 total across the two files).pnpm run typecheckpnpm run test:unit:allpnpm run lintpnpm run formatNo e2e change. The contract is
util.inspecton Node, which the unit test pins. workerd was not run; the added symbol isnodejs.util.inspect.custom, which workerd does not use.Notes
Node 24 was not run. The assertion accepts undici's
'X-Real': 'yes'spelling from Node 22.18, the same spellingrequest-headers.test.tsalready expects for request inspect.