Skip to content

redis: RedisClient.parseURL throws 'Value of URL.prototype.hostname called on an incompatible receiver' (new node_url_1.URL(...) in @redis/client) #11121

Description

@proggeramlug

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")));

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), and new node_url_1.URL("redis://x:1"): .hostname / .href throw the error above.
  • node_url_1.URL === URL is true. Object.prototype.toString.call(obj) is [object URL], but obj instanceof URL is false. The getter guard (url/prototype.rs require_url_receiver → is_url_object_shape) rejects the receiver.

Narrowing (all tried in a scratch compilePackages package next to @redis/client)

  • The same parseURL body in a small package (const node_url_1 = require("node:url") plus a function or a class extends EventEmitter) works. It also works with 8 preceding local requires, with __importDefault(require(...)), and after one @redis/client require.
  • 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-line class … 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 (only node: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

  1. added
    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings
    on Sep 23, 2026
  2. proggeramlug commented on Sep 25, 2026

    @proggeramlug
    ContributorAuthor

    Fixed by #11133, which landed via a merge train (feature detection now scans class bodies, so global-url/url-engine are enabled for URL uses inside class methods).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions