fix: handle undefined statusText in writeHead arguments#49
Open
mahmoodhamdi wants to merge 1 commit into
Open
Conversation
When writeHead is called with three arguments where the second argument (statusText) is undefined or null, the headers object at the third argument position was being ignored. This is because the argument parsing only checked if the second argument was a string to determine the header index, without considering that three arguments always means the third is headers. This caused headers set via writeHead(status, undefined, headers) to be silently dropped, which affected downstream packages like compression middleware (expressjs/compression#254). The fix checks if there are more than two arguments, and if so, always reads headers from the third position, matching Node.js native writeHead behavior.
bjohansebas
suggested changes
Jun 17, 2026
bjohansebas
left a comment
Member
There was a problem hiding this comment.
Nice catch, the fix is correct for the reported writeHead(status, undefined/null, headers) case.
While checking this against native Node across the full argument space, I noticed one edge case the headerIndex approach doesn't fully cover. This patch forwards the leading args ([statusCode, arguments[1]]) to the original writeHead, so when the 2nd argument is a non-string it gets re-applied by native as headers:
res.writeHead(200, { 'X-A': '1' }, { 'X-B': '2' })
// native → only X-B (reason object ignored)
// PR #49 → BOTH X-A and X-B (X-A leaks via the forwarded leading args)(writeHead(status, obj, undefined) also only works today by accident, the forwarded arguments[1] happens to be re-applied by native.)
A small refactor mirrors Node's rule exactly and stops forwarding a non-string reason:
function setWriteHeadHeaders (statusCode) {
var length = arguments.length
- var headerIndex = length > 1 && typeof arguments[1] === 'string'
- ? 2
- : length > 2
- ? 2
- : 1
-
- var headers = length >= headerIndex + 1
- ? arguments[headerIndex]
- : undefined
+ var hasReason = length > 1 && typeof arguments[1] === 'string'
+
+ // locate the headers argument, mirroring node's
+ // writeHead(statusCode[, statusMessage][, headers]):
+ // - with a string statusMessage, headers are the 3rd argument
+ // - otherwise node resolves headers as `headers ?? statusMessage`, so a
+ // non-string 2nd argument is only used when the 3rd is null/undefined
+ var headers
+ if (hasReason) {
+ headers = arguments[2]
+ } else if (length > 2) {
+ headers = arguments[2] != null ? arguments[2] : arguments[1]
+ } else {
+ headers = arguments[1]
+ }
this.statusCode = statusCode
if (Array.isArray(headers)) {
// handle array case
setHeadersFromArray(this, headers)
} else if (headers) {
// handle object case
setHeadersFromObject(this, headers)
}
- // copy leading arguments
- var args = new Array(Math.min(length, headerIndex))
- for (var i = 0; i < args.length; i++) {
- args[i] = arguments[i]
- }
+ // only forward the status code and a real (string) status message to the
+ // original writeHead; headers are already applied above via setHeader, and a
+ // non-string 2nd argument must not be forwarded or node would re-apply it as
+ // headers
+ var args = hasReason
+ ? [statusCode, arguments[1]]
+ : [statusCode]
return args
}Plus a test:
+ describe('writeHead(status, obj, obj)', function () {
+ it('should use the last object as headers and ignore a non-string statusMessage, matching node', function (done) {
+ var server = createServer(listener, handler)
+
+ function handler (req, res) {
+ res.writeHead(201, { 'X-First': 'a' }, { 'X-Second': 'b' })
+ }
+
+ function listener (req, res) {}
+
+ request(server)
+ .get('/')
+ .expect('X-Second', 'b')
+ .expect(201)
+ .expect(function (res) {
+ assert.strictEqual(res.headers['x-first'], undefined, 'X-First should be ignored')
+ })
+ .end(done)
+ })
+ })
This was referenced Jun 17, 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.
Summary
Fixes argument parsing in
setWriteHeadHeadersto correctly handlewriteHead(statusCode, undefined, headers)— where the statusText is explicitly passed asundefinedornull.Problem
When
res.writeHead(200, undefined, { 'X-Custom': 'value' })is called throughon-headers, the headers object at the third argument position is silently dropped.Root cause: The argument parsing logic determines the header index based only on whether
arguments[1]is a string:When
arguments[1]isundefined, it's not a string, soheaderIndex = 1. Thenarguments[1](which isundefined) is read as the headers, and the actual headers atarguments[2]are never accessed.Without
on-headers: Native Node.jswriteHead(200, undefined, headers)works correctly.With
on-headers: Headers are silently dropped.This was reported as expressjs/compression#254, where the compression middleware (which uses
on-headers) caused headers to be lost when the Vercel AI SDK calledwriteHead(status, undefined, headers).Fix
When there are more than 2 arguments, the headers are always at index 2 regardless of whether the second argument is a string, matching Node.js native
writeHeadbehavior:Testing
Added 2 new tests:
writeHead(status, undefined, obj)— headers are set correctlywriteHead(status, null, obj)— headers are set correctlyAll 26 tests pass. Lint clean.