{"id":"fa533cced956d87a","repo":"redis/node-redis","slug":"invalid-sentinelclientoptions-for-sentinel","errorCode":null,"errorMessage":"invalid sentinelClientOptions for Sentinel","messagePattern":"invalid sentinelClientOptions for Sentinel","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/sentinel/index.ts","lineNumber":885,"sourceCode":"    // 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;\n\n    if (this.#sentinelClientOptions.url !== undefined) {\n      throw new Error(\"invalid sentinelClientOptions for Sentinel\");\n    }\n\n    this.#masterClientQueue = new WaitQueue();\n    for (let i = 0; i < this.#masterPoolSize; i++) {\n      this.#masterClientQueue.push(i);\n    }\n\n    /* persistent object for life of sentinel object */\n    this.#pubSubProxy = new PubSubProxy(\n      this.#nodeClientOptions,\n      err => this.emit('error', err)\n    );\n  }\n\n  #createClient(\n    node: RedisNode,\n    clientOptions: AnyRedisClientOptions,\n    reconnectStrategy?: false","sourceCodeStart":867,"sourceCodeEnd":903,"githubUrl":"https://github.com/redis/node-redis/blob/bb5beb56578573910e2ee8f39681edc214c41398/packages/client/lib/sentinel/index.ts#L867-L903","documentation":"Thrown in the RedisSentinelInternal constructor when options.sentinelClientOptions contains a `url` field. The Sentinel topology discovers master/replica host:port pairs at runtime and builds connections from them, so a hard-coded `url` on the sentinel-monitor client would be contradictory and ignored. The constructor explicitly rejects this misconfiguration (sentinel/index.ts:884) the same way it rejects `url` on nodeClientOptions (sentinel/index.ts:863).","triggerScenarios":"Constructing RedisSentinelFactory / RedisSentinelClient with options.sentinelClientOptions = { url: 'redis://host:6379' }. Also triggered by passing a RedisClientOptions object built from a `redis://` URL string (e.g. via parseURL) as sentinelClientOptions.","commonSituations":"Copy-pasting a normal RedisClient options object (which often carries `url`) into the Sentinel `sentinelClientOptions` slot; reusing TLS/auth options derived from a URL for the sentinel monitor connections.","solutions":["Remove the `url` field from sentinelClientOptions — the Sentinel client supplies host/port per discovered node.","Move connection details into sentinelClientOptions.socket and sentinelClientOptions.{username,password} instead of a URL.","If you need URL-style config for the *data* nodes, put it on nodeClientOptions (also url-free) or use socket.host/port plus credentials.","Lint your config builder so sentinelClientOptions is always constructed object-literal, never from parseURL()."],"exampleFix":"// before\nconst sentinel = factory.createClient({\n  sentinelClientOptions: { url: 'rediss://sentinel:26379' }\n});\n// after\nconst sentinel = factory.createClient({\n  sentinelClientOptions: {\n    socket: { tls: true, reconnectStrategy: 1000 },\n    username: 'sentinelUser', password: process.env.SENTINEL_PASS\n  }\n});","handlingStrategy":"validation","validationCode":"function buildSentinelClientOptions(opts) {\n  if (opts && opts.url !== undefined) {\n    throw new Error('sentinelClientOptions must not contain a url; use socket + credentials');\n  }\n  return opts;\n}\n// before constructing the Sentinel client:\nconst sentinelClientOptions = buildSentinelClientOptions(rawOpts);","typeGuard":"function isValidSentinelClientOptions(o: unknown): o is Record<string, unknown> {\n  return typeof o === 'object' && o !== null && !('url' in o && o.url !== undefined);\n}","tryCatchPattern":"try {\n  const client = factory.createClient({ sentinelClientOptions });\n} catch (e) {\n  if (e instanceof Error && /invalid sentinelClientOptions/.test(e.message)) {\n    // strip url and retry, or surface a config error to the operator\n  }\n  throw e;\n}","preventionTips":["Construct sentinelClientOptions as an object literal; never spread a parseURL() result into it.","Add a unit test asserting your config builder never emits a `url` key in the sentinel options.","Share a single typed builder for sentinelClientOptions across environments."],"tags":["configuration","sentinel","typescript","constructor"],"analyzedSha":"bb5beb56578573910e2ee8f39681edc214c41398","analyzedAt":"2026-08-03T19:09:15.686Z","schemaVersion":2}