phacility/phabricator · error · PhabricatorWorkerPermanentFailureException

Subscription has invalid billing period.

Error message

Subscription has invalid billing period.

What it means

The billing period must have positive length: trigger.this-epoch must be strictly greater than last_epoch (the stored previous epoch, or the subscription creation date when absent). A zero or negative span means the epoch metadata is inconsistent, and the worker permanently fails rather than invoice an empty or backwards period.

Source

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

  private function getBillingPeriodRange(PhortuneSubscription $subscription) {
    $data = $this->getTaskData();

    $last_epoch = idx($data, 'trigger.last-epoch');
    if (!$last_epoch) {
      // If this is the first time the subscription is firing, use the
      // creation date as the start of the billing period.
      $last_epoch = $subscription->getDateCreated();
    }
    $this_epoch = idx($data, 'trigger.this-epoch');

    if (!$last_epoch || !$this_epoch) {
      throw new PhabricatorWorkerPermanentFailureException(
        pht('Subscription is missing billing period information.'));
    }

    $period_length = ($this_epoch - $last_epoch);
    if ($period_length <= 0) {
      throw new PhabricatorWorkerPermanentFailureException(
        pht(
          'Subscription has invalid billing period.'));
    }

    if (empty($data['manual'])) {
      if (PhabricatorTime::getNow() < $this_epoch) {
        throw new Exception(
          pht(
            'Refusing to generate a subscription invoice for a billing period '.
            'which ends in the future.'));
      }
    }

    return array($last_epoch, $this_epoch);
  }

}

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Compare the trigger.last-epoch and trigger.this-epoch values in the failed task's data against the expected billing dates
  2. Fix clock and timezone consistency (synced NTP, identical PHP date.timezone) across web and daemon hosts
  3. Re-create the subscription trigger by re-saving the billing period so fresh, correctly ordered epochs are written
Defensive patterns

Strategy: validation

Validate before calling

// Assert an ordered billing window before enqueueing the worker.
$last_epoch = idx($data, 'trigger.last-epoch', $subscription->getDateCreated());
$this_epoch = idx($data, 'trigger.this-epoch');
if (!$this_epoch || $this_epoch <= $last_epoch) {
  throw new Exception(pht('Refusing to queue a non-positive billing period.'));
}

Prevention

When it happens

Trigger: trigger.this-epoch is less than or equal to trigger.last-epoch in the task data (corrupted or hand-written epochs); the subscription's creation date is newer than this-epoch because of server clock skew or a backdated database restore; manual re-fire of an old stale task.

Common situations: NTP drift between the host that scheduled the trigger and the host processing the queue; restored dumps where dateCreated is newer than the queued epoch data; hand-built task data with swapped epoch keys.

Related errors


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