{"record":{"id":"c8f5eaa16c2cd163","repo":"phacility/phabricator","slug":"project-icons-must-have-unique-keys-but-two-icons","errorCode":null,"errorMessage":"Project icons must have unique keys, but two icons share the same key (\"%s\").","messagePattern":"Project icons must have unique keys, but two icons share the same key \\(\"(.+?)\"\\)\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/project/icon/PhabricatorProjectIconSet.php","lineNumber":255,"sourceCode":"      if ($special !== null) {\n        if (empty($valid[$special])) {\n          throw new Exception(\n            pht(\n              'Icon special attribute \"%s\" is not valid. Recognized special '.\n              'attributes are: %s.',\n              $special,\n              implode(', ', array_keys($valid))));\n        }\n      }\n    }\n\n    $default = null;\n    $milestone = null;\n    $keys = array();\n    foreach ($config as $idx => $value) {\n      $key = $value['key'];\n      if (isset($keys[$key])) {\n        throw new Exception(\n          pht(\n            'Project icons must have unique keys, but two icons share the '.\n            'same key (\"%s\").',\n            $key));\n      } else {\n        $keys[$key] = true;\n      }\n\n      $is_disabled = idx($value, 'disabled');\n\n      $image = idx($value, 'image');\n      if ($image !== null) {\n        $builtin = idx($value, 'image');\n        $builtin_map = id(new PhabricatorFilesOnDiskBuiltinFile())\n          ->getProjectBuiltinFiles();\n        $builtin_map = array_flip($builtin_map);\n\n        $root = dirname(phutil_get_library_root('phabricator'));","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/project/icon/PhabricatorProjectIconSet.php#L237-L273","documentation":"Icon keys must be unique across the whole projects.icons list. While collecting keys into a lookup array, a second specification reusing an existing key throws and names the duplicated key, before default/milestone resolution continues.","triggerScenarios":"Appending a custom icon with \"key\": \"tag\" while the builtin tag icon is still in the list, or merging two config fragments that both define the same key.","commonSituations":"Extending the default list instead of replacing it, and merging icon configs from multiple sources.","solutions":["Rename one of the duplicates to an unused lowercase key (letters only).","If you meant to replace a builtin icon, remove the original entry so only one spec carries that key."],"exampleFix":"// before: two entries share key \"tag\"\n[{\"key\":\"tag\",\"name\":\"Tag\",\"icon\":\"fa-tags\"}, {\"key\":\"tag\",\"name\":\"Tag2\",\"icon\":\"fa-tag\"}]\n// after\n[{\"key\":\"tag\",\"name\":\"Tag\",\"icon\":\"fa-tags\"}, {\"key\":\"label\",\"name\":\"Label\",\"icon\":\"fa-tag\"}]","handlingStrategy":"validation","validationCode":"$keys = array();\nforeach ($config as $entry) {\n  if (isset($keys[$entry['key']])) {\n    throw new Exception('duplicate icon key: '.$entry['key']);\n  }\n  $keys[$entry['key']] = true;\n}","typeGuard":"function has_unique_icon_keys(array $config) {\n  $keys = array_column($config, 'key');\n  return count($keys) === count(array_unique($keys));\n}","tryCatchPattern":null,"preventionTips":["When adding a custom icon, rename it or drop the builtin entry it replaces.","Lint merged config fragments for duplicate keys before saving."],"tags":["phabricator","configuration","project-icons","duplicate-key"],"backgroundTag":"duplicate-key-validation","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}