redis/redis-py · error · ValueError

Cluster client has no nodes - cannot create health check…

Error message

Cluster client has no nodes - cannot create health check client

What it means

Raised as ValueError by AbstractHealthCheckPolicy.get_client() (healthcheck.py:208-211) when the database's underlying client is a RedisCluster but its startup_nodes collection is empty, so there is no host/port to seed the health-check cluster client from. The health-check layer cannot construct a probe client without at least one known node.

Solutions

  1. Ensure the cluster DatabaseConfig provides a reachable seed host/port (via from_url with a valid redis://cluster-host:port or host/port in client_kwargs).
  2. Verify the cluster is up and CLUSTER NODES returns entries before constructing MultiDBClient.
  3. Construct the RedisCluster client separately, confirm client.startup_nodes is non-empty, then pass it via DatabaseConfig.client_kwargs or a custom Database.
  4. Add a startup assertion: assert client.startup_nodes, 'cluster has no startup nodes'.

Example fix

// before
db_cfg = DatabaseConfig(client_kwargs={"host": ""}, )  # cluster, no seed node
# or a cluster whose discovery yielded nothing

// after - supply a valid cluster seed URL
from redis.asyncio import RedisCluster
rc = RedisCluster.from_url("redis://cluster-seed.local:6379")
assert rc.startup_nodes  # fail fast with a clear cause
db_cfg = DatabaseConfig(client_kwargs={"startup_nodes": rc.startup_nodes})
Defensive patterns

Strategy: validation

Validate before calling

from redis.asyncio import RedisCluster

def cluster_has_startup_nodes(client_cls, kwargs, url) -> bool:
    rc = RedisCluster.from_url(url) if url else RedisCluster(**kwargs)
    return bool(rc.startup_nodes)

# before constructing MultiDBClient with a cluster DB:
assert cluster_has_startup_nodes(cfg.client_class, cfg.client_kwargs, cfg.from_url), \
    "cluster database has no startup nodes"

Type guard

from redis.asyncio import RedisCluster

def has_seed_nodes(rc: RedisCluster) -> bool:
    return isinstance(rc, RedisCluster) and bool(rc.startup_nodes)

Try / catch

try:
    await client.initialize()
except ValueError as e:
    if "no nodes" in str(e):
        # fix the cluster seed host/port in DatabaseConfig and reconstruct
        raise RuntimeError("cluster DatabaseConfig needs a reachable seed host/port")
    raise

Prevention

When it happens

Trigger: Configuring a DatabaseConfig with a cluster client (client_class=RedisCluster or a cluster URL) that was instantiated without usable startup nodes — e.g. constructed with an empty/malformed host list, a NodesManager that produced no startup nodes, or a cluster client that failed initial discovery and was then handed to MultiDBClient.

Common situations: Building RedisCluster from kwargs that omit host/port, pointing at a cluster URL whose CLUSTER NODES discovery returned nothing, race where the cluster client is passed to MultiDBClient before its node discovery completes, or a stale/closed cluster client whose startup_nodes were cleared.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/592b7d9c2c5d9b4a. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/multidb/healthcheck.py:209

                    # different names (``_require_full_coverage`` vs
                    # ``require_full_coverage``), so resolve it defensively to
                    # support a sync RedisCluster underlying client too.
                    require_full_coverage = getattr(
                        nodes_manager,
                        "require_full_coverage",
                        getattr(nodes_manager, "_require_full_coverage", True),
                    )
                    client = AsyncRedisCluster(
                        host=first_node.host,
                        port=first_node.port,
                        dynamic_startup_nodes=nodes_manager._dynamic_startup_nodes,
                        address_remap=nodes_manager.address_remap,
                        require_full_coverage=require_full_coverage,
                        retry=database.client.retry,
                        **filtered_kwargs,
                    )
                else:
                    raise ValueError(
                        "Cluster client has no nodes - cannot create health check client"
                    )
            else:
                raise TypeError(f"Unsupported client type: {type(database.client)}")
            self._clients[db_id] = client

        return client

    async def close(self) -> None:
        """Close all health check clients."""
        close_tasks = [
            asyncio.create_task(client.aclose()) for client in self._clients.values()
        ]

        if close_tasks:
            await asyncio.gather(*close_tasks, return_exceptions=True)

        self._clients.clear()

View on GitHub (pinned to 6a6b581b48)