{"record":{"id":"359f5c2a49b4f759","repo":"phacility/phabricator","slug":"search-cluster-has-no-hosts-for-role-s","errorCode":null,"errorMessage":"Search cluster has no hosts for role \"%s\".","messagePattern":"Search cluster has no hosts for role \"(.+?)\"\\.","errorType":"exception","errorClass":"PhabricatorClusterNoHostForRoleException","httpStatus":null,"severity":"error","filePath":"src/infrastructure/cluster/search/PhabricatorSearchService.php","lineNumber":129,"sourceCode":"  }\n\n\n  /**\n   * Get a random host reference with the specified role, skipping hosts which\n   * failed recent health checks.\n   * @throws PhabricatorClusterNoHostForRoleException if no healthy hosts match.\n   * @return PhabricatorSearchHost\n   */\n  public function getAnyHostForRole($role) {\n    $hosts = $this->getAllHostsForRole($role);\n    shuffle($hosts);\n    foreach ($hosts as $host) {\n      $health = $host->getHealthRecord();\n      if ($health->getIsHealthy()) {\n        return $host;\n      }\n    }\n    throw new PhabricatorClusterNoHostForRoleException($role);\n  }\n\n\n  /**\n   * Get all configured hosts for this service which have the specified role.\n   * @return PhabricatorSearchHost[]\n   */\n  public function getAllHostsForRole($role) {\n    // if the role is explicitly set to false at the top level, then all hosts\n    // have the role disabled.\n    if (idx($this->config, $role) === false) {\n      return array();\n    }\n\n    $hosts = array();\n    foreach ($this->hosts as $host) {\n      if ($host->hasRole($role)) {\n        $hosts[] = $host;","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/cluster/search/PhabricatorSearchService.php#L111-L147","documentation":"PhabricatorSearchService::getAnyHostForRole($role) picks a random host with the requested role (`read` or `write`) that is currently passing its health checks. It throws PhabricatorClusterNoHostForRoleException when no host has the role at all — including when the role is disabled service-wide via `roles => array('read' => false)` or a top-level `'read' => false` — or when every matching host is temporarily unhealthy after recent connection failures.","triggerScenarios":"Calling `getAnyHostForRole('write')` when all hosts set `roles => array('write' => false)`; the service config disables the role at the top level (`idx($this->config, $role) === false`); or the Elasticsearch/MySQL backend is down and health records have marked every host sick.","commonSituations":"Search backend outage while users run queries; read replicas marked non-readable during maintenance but code still requests reads; querying immediately after a network blip before health scores decay back to healthy.","solutions":["Inspect `getAllHostsForRole($role)`: an empty array means config — fix the `roles` maps or remove the service-level role disable.","If hosts exist but all are unhealthy, restore the backend (service, network, credentials) and let automatic health checks mark them healthy again; sick hosts are retried as their health record recovers.","Check `bin/search status` / the Search Servers console for per-host health and failure counts.","As a stopgap, configure a writable MySQL fulltext service so reads can be served while the primary backend recovers."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-check before requesting a host:\n$hosts = $service->getAllHostsForRole($role);\nif (!$hosts) {\n  // configuration problem: no host has this role (or role disabled at top level)\n  return handleNoHostConfigured($role);\n}\n$healthy = array_filter($hosts, function ($h) {\n  return $h->getHealthRecord()->getIsHealthy();\n});\nif (!$healthy) {\n  // all sick: backend outage, health checks will recover them\n  return deferOrFallback($role);\n}","typeGuard":null,"tryCatchPattern":"try {\n  $host = $service->getAnyHostForRole('read');\n} catch (PhabricatorClusterNoHostForRoleException $ex) {\n  if (!$service->getAllHostsForRole('read')) {\n    // permanent: fix cluster.search roles config\n    throw $ex;\n  }\n  // transient: all hosts unhealthy; bounded backoff, then surface\n  usleep(250000); // simple single retry backoff, do not loop unbounded\n  $host = $service->getAnyHostForRole('read');\n}","preventionTips":["Configure at least two hosts per role across services so one sick host cannot starve a role.","Monitor `bin/search status` and alert when health failure counts rise, before the last host flips unhealthy.","Never set `roles => array('read' => false)` on every host unless code paths requesting that role are also disabled."],"tags":["php","phabricator","search","cluster","health-check","elasticsearch"],"backgroundTag":"no-healthy-search-host","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}