{"record":{"id":"17290cf3ced478be","repo":"phacility/phabricator","slug":"search-cluster-configuration-is-not-valid-each-en","errorCode":null,"errorMessage":"Search cluster configuration is not valid: each entry in the list must be a dictionary describing a search service, but the value with index \"%s\" is not a dictionary.","messagePattern":"Search cluster configuration is not valid: each entry in the list must be a dictionary describing a search service, but the value with index \"(.+?)\" is not a dictionary\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php","lineNumber":19,"sourceCode":"<?php\n\nfinal class PhabricatorClusterSearchConfigType\n  extends PhabricatorJSONConfigType {\n\n  const TYPEKEY = 'cluster.search';\n\n  public function validateStoredValue(\n    PhabricatorConfigOption $option,\n    $value) {\n    self::validateValue($value);\n  }\n\n  public static function validateValue($value) {\n    $engines = PhabricatorSearchService::loadAllFulltextStorageEngines();\n\n    foreach ($value as $index => $spec) {\n      if (!is_array($spec)) {\n        throw new Exception(\n          pht(\n            'Search cluster configuration is not valid: each entry in the '.\n            'list must be a dictionary describing a search service, but '.\n            'the value with index \"%s\" is not a dictionary.',\n            $index));\n      }\n\n      try {\n        PhutilTypeSpec::checkMap(\n          $spec,\n          array(\n            'type'      => 'string',\n            'hosts'     => 'optional list<map<string, wild>>',\n            'roles'     => 'optional map<string, wild>',\n            'port'      => 'optional int',\n            'protocol'  => 'optional string',\n            'path'      => 'optional string',\n            'version'   => 'optional int',","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php#L1-L37","documentation":"`cluster.search` must be a list where every entry is a dictionary describing one search service. PhabricatorClusterSearchConfigType::validateValue iterates the list and throws when an entry is a scalar or a list — e.g. a bare engine name — instead of a map. Validation runs through validateStoredValue whenever the option is saved.","triggerScenarios":"Setting `cluster.search` to `array('elasticsearch')` (string entry), `array('mysql', 'elasticsearch')` (scalars), or a doubly-nested list, instead of `array(array('type' => 'mysql'))`. Triggered when saving the option via the config application or `bin/config set`.","commonSituations":"Hand-writing the config from memory instead of docs; converting config from YAML/JSON and losing a nesting level; older examples where a simpler format was shown.","solutions":["Wrap each service as a dictionary whose required first key is `type`, e.g. `array('type' => 'elasticsearch', 'hosts' => array(...))`.","Remove any scalar or plain-list entries from `cluster.search`.","Prefer the Config → Search Servers UI, which produces the correct structure."],"exampleFix":"// before\n'cluster.search' => array('elasticsearch'),\n// after\n'cluster.search' => array(\n  array(\n    'type' => 'elasticsearch',\n    'hosts' => array(\n      array('host' => 'es1.example.com', 'port' => 9200),\n    ),\n  ),\n),","handlingStrategy":"validation","validationCode":"// Before saving cluster.search, mirror the config-type check:\nfunction assertSearchEntriesAreMaps(array $cluster_search) {\n  foreach ($cluster_search as $index => $spec) {\n    if (!is_array($spec) || (array_keys($spec) === range(0, count($spec) - 1))) {\n      throw new Exception(\n        'cluster.search['.$index.'] must be a dictionary, e.g. '.\n        \"array('type' => 'mysql')\");\n    }\n  }\n}","typeGuard":"function isSearchServiceSpec($spec) {\n  return is_array($spec) && isset($spec['type']) && is_string($spec['type']);\n}","tryCatchPattern":null,"preventionTips":["Prefer the Config → Search Servers UI over hand-writing the raw value; it emits the correct structure.","Unit-test your config generation by running PhabricatorClusterSearchConfigType::validateValue() on the produced value in CI.","Start from a known-good example (`array(array('type' => 'mysql'))`) and add fields incrementally, saving after each change so validation catches drift early."],"tags":["php","phabricator","config","search","validation"],"backgroundTag":"search-cluster-config-invalid","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}