{"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/bb5beb56578573910e2ee8f39681edc214c41398/packages/client/lib/sentinel/index.ts#L846-L882","documentation":"RedisSentinelInternal rejects nodeClientOptions that contain a url field. Sentinel discovers master/replica host:port from Sentinel itself (and applies nodeAddressMap), so a static url on node clients would conflict with the discovered topology and is not allowed. Use socket.host/socket.port or nodeAddressMap for address remapping instead.","triggerScenarios":"Constructing createSentinel({ nodeClientOptions: { url: 'redis://...' } }). The constructor copies nodeClientOptions and explicitly checks for the presence of .url, throwing if set.","commonSituations":"Copying a RedisClientOptions object that was built with { url } from a standalone client into the sentinel config; attempting to pin a node address via url instead of nodeAddressMap; sharing a TLS/socket config object that happens to carry url.","solutions":["Remove the url field from nodeClientOptions — set socket.host/socket.port only if needed (usually not; Sentinel provides addresses).","Use nodeAddressMap to remap discovered Sentinel addresses to reachable host:port pairs (e.g. for Docker/TLS front-ends).","Put connection-level options like TLS in nodeClientOptions.socket without a url.","Destructure out url when reusing an existing options object: const { url, ...rest } = opts."],"exampleFix":"// before\ncreateSentinel({ nodeClientOptions: { url: 'redis://10.0.0.1:6379', socket: { tls: true } }, ... });\n\n// after\ncreateSentinel({ nodeClientOptions: { socket: { tls: true } }, nodeAddressMap: { '10.0.0.1:6379': 'redis.internal:6379' }, ... });","handlingStrategy":"validation","validationCode":"function validateNodeOpts(opts) { if (opts.nodeClientOptions?.url !== undefined) throw new Error('nodeClientOptions.url is forbidden for Sentinel'); }","typeGuard":"function sentinelNodeOptsValid(opts) { return opts.nodeClientOptions == null || opts.nodeClientOptions.url === undefined; }","tryCatchPattern":"// Construction-time error: strip url before constructing.","preventionTips":["Never put url in nodeClientOptions; let Sentinel discover addresses.","Use nodeAddressMap to remap host:port for NAT/Docker/TLS.","Destructure out url when reusing an options object: const { url, ...rest } = opts."],"tags":["sentinel","configuration","node-client-options","validation"],"analyzedSha":"bb5beb56578573910e2ee8f39681edc214c41398","analyzedAt":"2026-08-03T19:09:15.686Z","schemaVersion":2}