{"record":{"id":"4b6b998d9a2e6e08","repo":"phacility/phabricator","slug":"build-step-s-has-step-group-key-s-but-no-st","errorCode":null,"errorMessage":"Build step \"%s\" has step group key \"%s\", but no step group with that key exists.","messagePattern":"Build step \"(.+?)\" has step group key \"(.+?)\", but no step group with that key exists\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/harbormaster/controller/HarbormasterStepAddController.php","lineNumber":33,"sourceCode":"          PhabricatorPolicyCapability::CAN_EDIT,\n        ))\n      ->executeOne();\n    if (!$plan) {\n      return new Aphront404Response();\n    }\n\n    $plan_id = $plan->getID();\n    $cancel_uri = $this->getApplicationURI(\"plan/{$plan_id}/\");\n    $plan_title = pht('Plan %d', $plan_id);\n\n    $all = HarbormasterBuildStepImplementation::getImplementations();\n    $all = msort($all, 'getName');\n\n    $all_groups = HarbormasterBuildStepGroup::getAllGroups();\n    foreach ($all as $impl) {\n      $group_key = $impl->getBuildStepGroupKey();\n      if (empty($all_groups[$group_key])) {\n        throw new Exception(\n          pht(\n            'Build step \"%s\" has step group key \"%s\", but no step group '.\n            'with that key exists.',\n            get_class($impl),\n            $group_key));\n      }\n    }\n\n    $groups = mgroup($all, 'getBuildStepGroupKey');\n    $boxes = array();\n\n    $enabled_groups = HarbormasterBuildStepGroup::getAllEnabledGroups();\n    foreach ($enabled_groups as $group) {\n      $list = id(new PHUIObjectItemListView())\n        ->setNoDataString(\n          pht('This group has no available build steps.'));\n\n      $steps = idx($groups, $group->getGroupKey(), array());","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/harbormaster/controller/HarbormasterStepAddController.php#L15-L51","documentation":"The 'Add Step' screen enumerates every registered build step implementation (HarbormasterBuildStepImplementation::getImplementations()) and groups them by getBuildStepGroupKey(); the keys must correspond to a HarbormasterBuildStepGroup subclass discovered by PhutilClassMapQuery (each group exposes a GROUPKEY constant). This exception means some implementation returns a group key for which no group class exists, so the Add Step page cannot be rendered at all.","triggerScenarios":"A custom step implementation whose getBuildStepGroupKey() returns a misspelled or invented key (e.g. 'custom/group' instead of an existing GROUPKEY); a custom group class that is not loaded by the library class map (so the key never registers); an upgrade that renamed a group key while an out-of-tree step still returns the old one.","commonSituations":"Deploying a custom Phabricator extension that adds a step but forgets the matching group class, or forgets to add the class to the library map; running an unrebased fork after upstream renamed group keys.","solutions":["Make the step return an existing group key (e.g. the key of the default/normal group) unless it needs its own section.","If a new section is intended, define a HarbormasterBuildStepGroup subclass with the matching GROUPKEY constant, load it via the library class map (phutil library map regeneration), and clear the class cache.","Fix typos: the string in getBuildStepGroupKey() must byte-for-byte equal some group's GROUPKEY.","After fixing, reload the Add Step page; the exception is per-request, no data is affected."],"exampleFix":"// before (custom step)\npublic function getBuildStepGroupKey() {\n  return 'mycustom';\n}\n\n// after (custom step reuses an existing group)\npublic function getBuildStepGroupKey() {\n  return HarbormasterNormalBuildStepGroup::GROUPKEY;\n}\n\n// after (or register the group it wants)\nfinal class MyCustomBuildStepGroup extends HarbormasterBuildStepGroup {\n  const GROUPKEY = 'mycustom';\n  public function getGroupName() { return pht('Custom Steps'); }\n  public function getGroupOrder() { return 9000; }\n}","handlingStrategy":"type-guard","validationCode":"// Extension boot check: fail at startup, not on the Add Step page\n$all_groups = HarbormasterBuildStepGroup::getAllGroups();\nforeach (HarbormasterBuildStepImplementation::getImplementations() as $impl) {\n  if (empty($all_groups[$impl->getBuildStepGroupKey()])) {\n    throw new PhutilProxyException('bad step group', ...); // during extension init\n  }\n}","typeGuard":"function stepGroupKeyIsRegistered($key) {\n  return array_key_exists($key, HarbormasterBuildStepGroup::getAllGroups());\n}\n// usage in the step:\npublic function getBuildStepGroupKey() {\n  $key = 'mycustom';\n  if (!stepGroupKeyIsRegistered($key)) {\n    return HarbormasterNormalBuildStepGroup::GROUPKEY;\n  }\n  return $key;\n}","tryCatchPattern":null,"preventionTips":["Add a unit test in every custom step extension asserting getBuildStepGroupKey() returns a key present in HarbormasterBuildStepGroup::getAllGroups().","Register every custom group class in the phutil library map and regenerate the map before deploying.","Rebase custom extensions after upstream upgrades; renamed GROUPKEY constants are a common breakage."],"tags":["harbormaster","build-step","extension","class-map","configuration"],"backgroundTag":"extension-registration-mismatch","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}