{"record":{"id":"83cd0266871128da","repo":"phacility/phabricator","slug":"refusing-to-generate-a-subscription-invoice-for-a","errorCode":null,"errorMessage":"Refusing to generate a subscription invoice for a billing period which ends in the future.","messagePattern":"Refusing to generate a subscription invoice for a billing period which ends in the future\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"src/applications/phortune/worker/PhortuneSubscriptionWorker.php","lineNumber":254,"sourceCode":"      $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":236,"sourceCodeEnd":265,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/phortune/worker/PhortuneSubscriptionWorker.php#L236-L265","documentation":"A safety valve in the billing worker: unless the task data carries the manual flag, it refuses to invoice a period whose end (this_epoch) is still in the future. Unlike its sibling errors this is a plain Exception, so the queue treats it as a temporary failure and retries with backoff; it self-heals once the period actually elapses.","triggerScenarios":"The trigger fires before this_epoch has passed (scheduler race, clock skew between the scheduling and executing hosts), or someone fires the worker early without setting data['manual'].","commonSituations":"Clock drift between hosts in multi-server installs; testing billing with artificially shifted clocks; trying to invoice early by invoking the worker directly instead of the UI action.","solutions":["Wait: the task retries automatically and succeeds once the current time passes this_epoch","Fix clock skew and keep date.timezone consistent across web and daemon hosts","To invoice early on purpose, use the subscription's invoice-now action, which queues the task with the manual flag set"],"exampleFix":"// before: firing the worker for a period that has not elapsed\n$data = array('subscriptionPHID' => $subscription->getPHID());\n\n// after: mark the run as manual to permit early invoicing\n$data = array(\n  'subscriptionPHID' => $subscription->getPHID(),\n  'manual' => true,\n);","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  // invoke subscription billing manually\n} catch (Exception $ex) {\n  if (strpos($ex->getMessage(), 'ends in the future') !== false) {\n    // Temporary by design: reschedule the run for $this_epoch; do not alert.\n  }\n}","preventionTips":["Let the daemon retry: the task succeeds once the period elapses","Use the invoice-now action (manual flag) when early invoicing is intentional","Keep scheduling and executing hosts time-synced to avoid firing before the window closes"],"tags":["phabricator","phortune","subscription","billing-period","clock-skew","temporary-failure"],"backgroundTag":"scheduled-too-early","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}