response.headers is an empty object on an axios response under Perry, where Node populates it. Status codes and response bodies are correct; only the headers object is unpopulated.
Found while validating PR #10679 (remove the axios native binding). It is pre-existing and not caused by that PR — see the attribution below — so #10679 is not blocked by it.
Observed
Real npm axios compiled from source (perry.compilePackages wildcard, 27-package transitive graph, 130 native modules), GET and POST against a local server:
|
Perry |
Node 26.5.1 |
res.status |
correct |
correct |
res.data |
correct |
correct |
Object.keys(res.headers) |
[] |
5 keys |
res.headers["content-type"] |
undefined |
"application/json" |
Not a casing problem: res.headers["Content-Type"] is also undefined, and the object is genuinely empty rather than differently keyed.
Attribution — four steps, in increasing cost
1. Dependency-free node:http control, no axios anywhere. Does not diverge: content-type reads application/json, correctly cased and a string, on both Perry and Node. The only difference is content-length vs transfer-encoding framing, which is unrelated. So this is not a generic client-transport gap — node:http's own res.headers works.
2. Absent, not miscased or mistyped. Object.keys() is empty, the value is undefined, typeof is "undefined". The three possibilities have different owners and this is the first.
3. Not #10668's documented limitation. That PR records a known gap in rawHeaders' name casing on the array form, and explicitly states that res.headers — the lower-cased-key object used here — is correct. Checked by reading the PR body rather than assuming from the title.
4. Worse on pristine main with the native binding in place. Verified the native binding was actually the one serving the import (compile log: serving axios from the bundled native binding perry-ext-axios... ignoring your installed node_modules/axios, Found 1 module(s): 1 native). Result there:
GET status: undefined GET data: undefined
POST status: undefined POST data: undefined
crash: Cannot read properties of undefined (reading 'content-type')
The entire response object is broken under the native binding, not merely its headers.
Consequence
Real-source axios is strictly better than the native binding on this path — it gets status and data right and leaves only headers incomplete, where the native binding gets nothing right and then crashes. That is a point in favour of the removal, not against it.
The remaining gap is axios's own header-population logic not working under Perry, independent of which axios implementation is used. Since node:http's res.headers is correct (step 1), the defect is in how axios reads or assembles headers from the response — likely its AxiosHeaders construction from the underlying response — rather than in the client transport itself. That is the place to look first.
response.headersis an empty object on an axios response under Perry, where Node populates it. Status codes and response bodies are correct; only the headers object is unpopulated.Found while validating PR #10679 (remove the axios native binding). It is pre-existing and not caused by that PR — see the attribution below — so #10679 is not blocked by it.
Observed
Real npm axios compiled from source (
perry.compilePackageswildcard, 27-package transitive graph, 130 native modules), GET and POST against a local server:res.statusres.dataObject.keys(res.headers)[]res.headers["content-type"]undefined"application/json"Not a casing problem:
res.headers["Content-Type"]is alsoundefined, and the object is genuinely empty rather than differently keyed.Attribution — four steps, in increasing cost
1. Dependency-free
node:httpcontrol, no axios anywhere. Does not diverge:content-typereadsapplication/json, correctly cased and a string, on both Perry and Node. The only difference iscontent-lengthvstransfer-encodingframing, which is unrelated. So this is not a generic client-transport gap —node:http's ownres.headersworks.2. Absent, not miscased or mistyped.
Object.keys()is empty, the value isundefined,typeofis"undefined". The three possibilities have different owners and this is the first.3. Not #10668's documented limitation. That PR records a known gap in
rawHeaders' name casing on the array form, and explicitly states thatres.headers— the lower-cased-key object used here — is correct. Checked by reading the PR body rather than assuming from the title.4. Worse on pristine
mainwith the native binding in place. Verified the native binding was actually the one serving the import (compile log:serving axios from the bundled native binding perry-ext-axios... ignoring your installed node_modules/axios,Found 1 module(s): 1 native). Result there:The entire response object is broken under the native binding, not merely its headers.
Consequence
Real-source axios is strictly better than the native binding on this path — it gets status and data right and leaves only headers incomplete, where the native binding gets nothing right and then crashes. That is a point in favour of the removal, not against it.
The remaining gap is axios's own header-population logic not working under Perry, independent of which axios implementation is used. Since
node:http'sres.headersis correct (step 1), the defect is in how axios reads or assembles headers from the response — likely itsAxiosHeadersconstruction from the underlying response — rather than in the client transport itself. That is the place to look first.