phacility/phabricator · error · PhabricatorWorkerPermanentFailureException

Failed to load subscription with PHID "%s".

Error message

Failed to load subscription with PHID "%s".

What it means

The queued billing task carries subscriptionPHID in its task data, and loadSubscription() re-loads it with an omnipotent-user PhortuneSubscriptionQuery. If the row is gone (almost always because the subscription was deleted), the query returns null and the worker throws a permanent failure. Retrying cannot resurrect a deleted subscription, so the queue discards the task.

Source

Thrown at src/applications/phortune/worker/PhortuneSubscriptionWorker.php:213


  /**
   * Load the subscription to generate an invoice for.
   *
   * @return PhortuneSubscription The subscription to invoice.
   */
  private function loadSubscription() {
    $viewer = PhabricatorUser::getOmnipotentUser();

    $data = $this->getTaskData();
    $subscription_phid = idx($data, 'subscriptionPHID');

    $subscription = id(new PhortuneSubscriptionQuery())
      ->setViewer($viewer)
      ->withPHIDs(array($subscription_phid))
      ->executeOne();
    if (!$subscription) {
      throw new PhabricatorWorkerPermanentFailureException(
        pht(
          'Failed to load subscription with PHID "%s".',
          $subscription_phid));
    }

    return $subscription;
  }


  /**
   * Get the start and end epoch timestamps for this billing period.
   *
   * @param PhortuneSubscription The subscription being billed.
   * @return pair<int, int> Beginning and end of the billing range.
   */
  private function getBillingPeriodRange(PhortuneSubscription $subscription) {
    $data = $this->getTaskData();

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Check the phortune_subscription table for the PHID shown in the failed task's data to confirm the subscription is really gone
  2. If the deletion was intentional, no action is needed: let the task record its permanent failure and move on
  3. If triggers keep firing, find and remove the orphaned row in the trigger (phabricator_trigger) table that keeps scheduling tasks for the dead subscription
Defensive patterns

Strategy: validation

Validate before calling

// Verify the subscription still exists before queueing billing work.
$subscription = id(new PhortuneSubscriptionQuery())
  ->setViewer(PhabricatorUser::getOmnipotentUser())
  ->withPHIDs(array($subscription_phid))
  ->executeOne();
if (!$subscription) {
  return; // do not schedule billing for a subscription that is gone
}

Try / catch

try {
  // scheduling or executing subscription trigger tasks
} catch (PhabricatorWorkerPermanentFailureException $ex) {
  if (strpos($ex->getMessage(), 'Failed to load subscription') !== false) {
  // orphaned trigger: remove the trigger row instead of re-queueing
  }
}

Prevention

When it happens

Trigger: A subscription's periodic trigger task fires after the PhortuneSubscription row was deleted; or the task data was hand-edited or re-queued with a PHID that does not exist in the database.

Common situations: User deletes a subscription while its trigger task is already in the queue; restoring a database dump while keeping the old worker queue; scripts enqueueing worker tasks with stale or hand-copied PHIDs.

Related errors


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