{"record":{"id":"ac857d6df67d4e99","repo":"phacility/phabricator","slug":"search-engine-configuration-has-an-invalid-service","errorCode":null,"errorMessage":"Search engine configuration has an invalid service specification (at index \"%s\"): %s.","messagePattern":"Search engine configuration has an invalid service specification \\(at index \"(.+?)\"\\): (.+?)\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php","lineNumber":40,"sourceCode":"            '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',\n          ));\n      } catch (Exception $ex) {\n        throw new Exception(\n          pht(\n            'Search engine configuration has an invalid service '.\n            'specification (at index \"%s\"): %s.',\n            $index,\n            $ex->getMessage()));\n      }\n\n      if (!array_key_exists($spec['type'], $engines)) {\n        throw new Exception(\n          pht(\n            'Invalid search engine type: %s. Valid types are: %s.',\n            $spec['type'],\n            implode(', ', array_keys($engines))));\n      }\n\n      if (isset($spec['hosts'])) {\n        foreach ($spec['hosts'] as $hostindex => $host) {\n          try {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php#L22-L58","documentation":"Each service dictionary in `cluster.search` is type-checked with PhutilTypeSpec::checkMap against the allowed schema: `type` (required string) plus optional `hosts` (list of maps), `roles` (map), `port` (int), `protocol` (string), `path` (string), `version` (int). Any unknown key, missing `type`, or wrong value type is caught and rethrown with the index and the underlying check failure appended as the `%s` detail.","triggerScenarios":"Writing `'host' => ...` instead of `'hosts'`; adding an unsupported key such as `'timeout'` or `'engine'`; setting `'port' => '9200'` (string) instead of `9200` (int); omitting the required `'type'` key; giving `hosts` a map instead of a list.","commonSituations":"Transcribing an Elasticsearch URL into config by hand and splitting fields differently than the schema expects; carrying keys over from a different Phabricator version; following outdated documentation.","solutions":["Read the trailing detail in the message — PhutilTypeSpec names the exact offending key and expected type.","Restrict each service map to type/hosts/roles/port/protocol/path/version with matching types (strings for protocol/path, ints for port/version).","Move per-server connection details into a `hosts` entry (host/roles/port/protocol/path/version), not to the service level."],"exampleFix":"// before\narray(\n  'type' => 'elasticsearch',\n  'host' => 'es1.example.com',\n  'port' => '9200',\n),\n// after\narray(\n  'type' => 'elasticsearch',\n  'hosts' => array(\n    array('host' => 'es1.example.com', 'port' => 9200),\n  ),\n),","handlingStrategy":"validation","validationCode":"$service_schema = 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',\n);\nPhutilTypeSpec::checkMap($spec, $service_schema); // throws with the offending key","typeGuard":"function isSearchServiceSpec($spec) {\n  return is_array($spec)\n    && isset($spec['type']) && is_string($spec['type'])\n    && (!isset($spec['port']) || is_int($spec['port']))\n    && (!isset($spec['version']) || is_int($spec['version']));\n}","tryCatchPattern":null,"preventionTips":["Do not invent keys — if a knob is not in the schema (type/hosts/roles/port/protocol/path/version), it is silently unsupported or rejected here.","Keep ints unquoted in config files; `'9200'` fails where 9200 passes.","Re-run PhabricatorClusterSearchConfigType::validateValue() in a test whenever you template cluster.search programmatically."],"tags":["php","phabricator","config","search","validation","schema"],"backgroundTag":"search-cluster-config-invalid","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}