{"record":{"id":"2faac772b75f4319","repo":"redis/node-redis","slug":"invalid-nodeclientoptions-for-sentinel","errorCode":null,"errorMessage":"invalid nodeClientOptions for Sentinel","messagePattern":"invalid nodeClientOptions for Sentinel","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/sentinel/index.ts","lineNumber":864,"sourceCode":"    this.#name = options.name;\n    this.#sentinelClientId = sentinelClientId;\n\n    this.#RESP = options.RESP;\n    this.#keyPrefix = options.keyPrefix;\n    this.#sentinelSeedNodes = Array.from(options.sentinelRootNodes);\n    // Initial root nodes start as a copy of the seed nodes; transform() later\n    // merges discovered nodes on top while preserving these seeds.\n    this.#sentinelRootNodes = Array.from(this.#sentinelSeedNodes);\n    this.#maxCommandRediscovers = options.maxCommandRediscovers ?? 16;\n    this.#masterPoolSize = options.masterPoolSize ?? 1;\n    this.#replicaPoolSize = options.replicaPoolSize ?? 0;\n    this.#nodeAddressMap = options.nodeAddressMap;\n    this.#scanInterval = options.scanInterval ?? 0;\n    this.#passthroughClientErrorEvents = options.passthroughClientErrorEvents ?? false;\n\n    this.#nodeClientOptions = (options.nodeClientOptions ? {...options.nodeClientOptions} : {}) as RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;\n    if (this.#nodeClientOptions.url !== undefined) {\n      throw new Error(\"invalid nodeClientOptions for Sentinel\");\n    }\n    // One fieldset registry across master/replica node clients: fieldsets registered before\n    // a failover must be transparently re-preparable on the promoted master's connections.\n    // (Sentinel-monitor clients use #sentinelClientOptions and never run HIMPORT.)\n    this.#nodeClientOptions.himportRegistry = new FieldsetRegistry();\n\n    if (options.clientSideCache) {\n      if (options.clientSideCache instanceof PooledClientSideCacheProvider) {\n        this.#clientSideCache = this.#nodeClientOptions.clientSideCache = options.clientSideCache;\n      } else {\n        const cscConfig = options.clientSideCache;\n        this.#clientSideCache = this.#nodeClientOptions.clientSideCache = new BasicPooledClientSideCache(cscConfig);\n//        this.#clientSideCache = this.#nodeClientOptions.clientSideCache = new PooledNoRedirectClientSideCache(cscConfig);\n      }\n    }\n\n    this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>, options.sentinelClientOptions) : {};\n    this.#sentinelClientOptions.modules = RedisSentinelModule;","sourceCodeStart":846,"sourceCodeEnd":882,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/sentinel/index.ts#L846-L882","documentation":"The Sentinel client discovers master/replica host:port pairs itself and injects them into each per-node `RedisClient` via `socket.host`/`socket.port`. A `url` field inside `nodeClientOptions` would name a fixed endpoint that conflicts with that discovery, so the constructor rejects it. The check runs only on the node (master/replica) options, not the sentinel-monitor options.","triggerScenarios":"Passing `nodeClientOptions: { url: 'redis://...' }` (or any object whose `url` is not undefined) to `new RedisSentinel(...)`. Throws synchronously in the constructor.","commonSituations":"Reusing a `RedisClientOptions` literal that already carries a `url` for a standalone client; templating options from config that includes a connection URL; spreading a shared default options object.","solutions":["Remove `url` from `nodeClientOptions`; let Sentinel pick endpoints from the master name and root nodes.","Move authentication and socket settings (password, TLS, family) into `nodeClientOptions` but never `url`.","Provide master/replica endpoints through `rootNodes` / the sentinel master `name`, not through node options."],"exampleFix":"// before\nnew RedisSentinel({ name: 'mymaster', nodeClientOptions: { url: 'redis://:secret@host:6379' } }, id);\n// after\nnew RedisSentinel({ name: 'mymaster', rootNodes: [{ host: 'sentinel', port: 26379 }], nodeClientOptions: { password: 'secret' } }, id);","handlingStrategy":"validation","validationCode":"function assertNodeOpts(opts: { nodeClientOptions?: { url?: unknown } }) {\n  if (opts.nodeClientOptions && opts.nodeClientOptions.url !== undefined) {\n    throw new Error('nodeClientOptions.url is forbidden for Sentinel; use rootNodes');\n  }\n}\nassertNodeOpts(opts);","typeGuard":"function hasNoNodeUrl(opts: { nodeClientOptions?: { url?: unknown } }): boolean {\n  return opts.nodeClientOptions?.url === undefined;\n}","tryCatchPattern":"try { new RedisSentinel(opts, id); }\ncatch (e) {\n  if (String(e).includes('invalid nodeClientOptions')) {\n    const { url, ...rest } = opts.nodeClientOptions ?? {};\n    opts = { ...opts, nodeClientOptions: rest };\n    new RedisSentinel(opts, id);\n  } else throw e;\n}","preventionTips":["Never put a connection URL in nodeClientOptions; Sentinel owns endpoint selection.","Run a lint-style assert on options before constructing Sentinel clients."],"tags":["sentinel","configuration","url","constructor","node-options"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}