phacility/phabricator · error · Exception

Failed to load build plan ("%s").

Error message

Failed to load build plan ("%s").

What it means

HarbormasterBuildable::applyBuildPlans() receives build requests, each naming a build plan PHID, loads all plans in one query, and throws if any request's plan fails to load. Since the viewer is omnipotent, a miss means the plan row no longer exists — a HarbormasterBuildRequest is referencing a deleted build plan. Note that disabled plans are handled separately (skipped), so this is strictly about missing plans.

Source

Thrown at src/applications/harbormaster/storage/HarbormasterBuildable.php:106

    $buildable = self::createOrLoadExisting(
      $viewer,
      $phid,
      $container_phid);

    $plan_phids = mpull($requests, 'getBuildPlanPHID');
    $plans = id(new HarbormasterBuildPlanQuery())
      ->setViewer($viewer)
      ->withPHIDs($plan_phids)
      ->execute();
    $plans = mpull($plans, null, 'getPHID');

    foreach ($requests as $request) {
      $plan_phid = $request->getBuildPlanPHID();
      $plan = idx($plans, $plan_phid);

      if (!$plan) {
        throw new Exception(
          pht(
            'Failed to load build plan ("%s").',
            $plan_phid));
      }

      if ($plan->isDisabled()) {
        // TODO: This should be communicated more clearly -- maybe we should
        // create the build but set the status to "disabled" or "derelict".
        continue;
      }

      $parameters = $request->getBuildParameters();
      $buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID());
    }
  }

  public function applyPlan(
    HarbormasterBuildPlan $plan,

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Identify the dangling request: search harbormaster_buildrequest for the failing plan PHID and delete the orphaned rows (the buildable can then be re-triggered).
  2. Update or delete the Herald rule / automation that still selects the deleted plan so new requests stop referencing it.
  3. If the plan was deleted in error, restore it from a backup so pending requests can be processed.
  4. After cleanup, re-run the daemon task or re-trigger the build (e.g. 'Request Build' / add a comment) to confirm builds start.
Defensive patterns

Strategy: validation

Validate before calling

// Before processing build requests, drop or fix requests whose plan is gone:
$plan_phids = mpull($requests, 'getBuildPlanPHID');
$plans = id(new HarbormasterBuildPlanQuery())
  ->setViewer(PhabricatorUser::getOmnipotentUser())
  ->withPHIDs($plan_phids)
  ->execute();
foreach ($requests as $request) {
  if (empty($plans[$request->getBuildPlanPHID()])) {
    // orphaned request - delete it instead of letting applyBuildPlans throw
  }
}

Prevention

When it happens

Trigger: A build request was created for a plan, and the plan was deleted before the request was processed (e.g. revision created, Herald applied a plan, plan deleted, daemon then runs applyBuildPlans). The exception surfaces in the worker task that starts builds for the buildable.

Common situations: Deleting build plans that are still selected by Herald rules or referenced by queued build requests; plan deleted via CLI/scripts without cleaning up requests; stale harbormaster_buildrequest rows after imports.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/3a3d00462c950cfe. Report an issue: GitHub.