Repository navigation
redis: RedisClient.parseURL throws 'Value of URL.prototype.hostname called on an incompatible receiver' (new node_url_1.URL(...) in @redis/client) #11121
Description
Summary
With redis@6.1.0 compiled through perry.compilePackages, RedisClient.parseURL throws:
TypeError: Value of URL.prototype.hostname called on an incompatible receiver
In Node it returns the parsed options. This is the next blocker for createClient({ url }) once #11042 is fixed: #initiateOptions in the constructor calls parseOptions → parseURL. (Before the #11042 fix the constructor never got here, because new Client(options) ran the wrong parent constructor.)
No server is needed to reproduce it.
Repro
package.json:
{
"dependencies": { "redis": "6.1.0" },
"perry": {
"compilePackages": ["redis", "@redis/client", "@redis/bloom", "@redis/json", "@redis/search", "@redis/time-series", "cluster-key-slot"],
"allow": { "compilePackages": ["redis", "@redis/client", "@redis/bloom", "@redis/json", "@redis/search", "@redis/time-series", "cluster-key-slot"] }
}
}main.ts:
const { RedisClient } = require("@redis/client");
console.log(JSON.stringify(RedisClient.parseURL("redis://127.0.0.1:6379")));- Node 26.5.1:
{"socket":{"host":"127.0.0.1","tls":false,"port":6379}} - Perry (
origin/maind8f24f1, and the redis: EventEmitter validation throws "argument must be an instance of EventEmitter. Received type string ('error')" on connect #11042 fix branch):TypeError: Value of URL.prototype.hostname called on an incompatible receiver
The code involved (@redis/client/dist/lib/client/index.js) is:
const node_url_1 = require("node:url");
// … inside class RedisClient extends node_events_1.EventEmitter:
static parseURL(url) {
const { hostname, port, protocol, username, password, pathname } = new node_url_1.URL(url), parsed = { … };What was measured (probes edited into that module)
new URL(url).hostname(the global): works.new node_url_1.URL(url),const U = node_url_1.URL; new U(url), andnew node_url_1.URL("redis://x:1"):.hostname/.hrefthrow the error above.node_url_1.URL === URListrue.Object.prototype.toString.call(obj)is[object URL], butobj instanceof URLisfalse. The getter guard (url/prototype.rsrequire_url_receiver→is_url_object_shape) rejects the receiver.
Narrowing (all tried in a scratch compilePackages package next to @redis/client)
- The same
parseURLbody in a small package (const node_url_1 = require("node:url")plus a function or a classextends EventEmitter) works. It also works with 8 preceding local requires, with__importDefault(require(...)), and after one@redis/clientrequire. - A package made of
client/index.js's own prelude (the TS helpers, then its 22 requires in the original order, with relative paths rewritten to@redis/client/dist/lib/...) followed by a 6-lineclass … extends node_events_1.EventEmitter { static parseURL(url) { … new node_url_1.URL(url) … } }fails. It still fails with any one of five subsets of those requires removed. - The same prelude with the
node:*requires moved to the top passes. So do the full prelude with a plain function instead of the class, and a trimmed prelude (onlynode:events,errors,node:url) with the class.
So the trigger depends on the module's require prelude and graph, not only on the URL construction. I have not narrowed it further. It is not GC-related: it is deterministic.
Found while validating the fix for #11042 (unblocks removing the native redis/ioredis binding).
Activity
changelog: #11133 feature detection scans class bodies (#11121)
fix(compile): feature detection scans class bodies for every gate (#1…
fix(compile): url-engine gate covers URL-as-a-value and dynamic node:…
test(#11121): make the gap fixture a .cts so node runs its require
changelog: #11133 feature detection scans class bodies (#11121)
fix(compile): feature detection scans class bodies for every gate (#1…
fix(compile): url-engine gate covers URL-as-a-value and dynamic node:…
test(#11121): make the gap fixture a .cts so node runs its require
changelog: #11133 feature detection scans class bodies (#11121)
fix(compile): feature detection scans class bodies for every gate (#1…
fix(compile): url-engine gate covers URL-as-a-value and dynamic node:…
test(#11121): make the gap fixture a .cts so node runs its require
changelog: #11133 feature detection scans class bodies (#11121)
fix(compile): feature detection scans class bodies for every gate (#1…
fix(compile): url-engine gate covers URL-as-a-value and dynamic node:…
test(#11121): make the gap fixture a .cts so node runs its require
changelog: #11133 feature detection scans class bodies (#11121)
proggeramlug commentedon Sep 25, 2026
Fixed by #11133, which landed via a merge train (feature detection now scans class bodies, so
global-url/url-engineare enabled for URL uses inside class methods).