{"record":{"id":"aa0114915a73dd34","repo":"phacility/phabricator","slug":"subscription-has-invalid-billing-period","errorCode":null,"errorMessage":"Subscription has invalid billing period.","messagePattern":"Subscription has invalid billing period\\.","errorType":"exception","errorClass":"PhabricatorWorkerPermanentFailureException","httpStatus":null,"severity":"error","filePath":"src/applications/phortune/worker/PhortuneSubscriptionWorker.php","lineNumber":247,"sourceCode":"  private function getBillingPeriodRange(PhortuneSubscription $subscription) {\n    $data = $this->getTaskData();\n\n    $last_epoch = idx($data, 'trigger.last-epoch');\n    if (!$last_epoch) {\n      // If this is the first time the subscription is firing, use the\n      // creation date as the start of the billing period.\n      $last_epoch = $subscription->getDateCreated();\n    }\n    $this_epoch = idx($data, 'trigger.this-epoch');\n\n    if (!$last_epoch || !$this_epoch) {\n      throw new PhabricatorWorkerPermanentFailureException(\n        pht('Subscription is missing billing period information.'));\n    }\n\n    $period_length = ($this_epoch - $last_epoch);\n    if ($period_length <= 0) {\n      throw new PhabricatorWorkerPermanentFailureException(\n        pht(\n          'Subscription has invalid billing period.'));\n    }\n\n    if (empty($data['manual'])) {\n      if (PhabricatorTime::getNow() < $this_epoch) {\n        throw new Exception(\n          pht(\n            'Refusing to generate a subscription invoice for a billing period '.\n            'which ends in the future.'));\n      }\n    }\n\n    return array($last_epoch, $this_epoch);\n  }\n\n}\n","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/worker/PhortuneSubscriptionWorker.php#L229-L265","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Compare the trigger.last-epoch and trigger.this-epoch values in the failed task's data against the expected billing dates","Fix clock and timezone consistency (synced NTP, identical PHP date.timezone) across web and daemon hosts","Re-create the subscription trigger by re-saving the billing period so fresh, correctly ordered epochs are written"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Assert an ordered billing window before enqueueing the worker.\n$last_epoch = idx($data, 'trigger.last-epoch', $subscription->getDateCreated());\n$this_epoch = idx($data, 'trigger.this-epoch');\nif (!$this_epoch || $this_epoch <= $last_epoch) {\n  throw new Exception(pht('Refusing to queue a non-positive billing period.'));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep clocks NTP-synced and date.timezone identical on web and daemon hosts","Treat epoch ordering as an invariant whenever task data is generated or copied","After database restores, rebuild subscription triggers instead of replaying old queued tasks"],"tags":["phabricator","phortune","subscription","billing-period","clock-skew","permanent-failure"],"backgroundTag":"invalid-date-range","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}