phacility/phabricator · error · Exception

Event queries which generate ghost events must include eithe

Error message

Event queries which generate ghost events must include either a result limit or an end date, because they may otherwise generate an infinite number of results. This query has neither.

What it means

PhabricatorCalendarEventQuery throws when setGenerateGhosts(true) is used without either a result limit (setLimit) or an end date (withDateRange). Ghost generation materializes every occurrence of every recurring event matching the query, which is unbounded without one of those caps, so the query refuses rather than loop forever.

Source

Thrown at src/applications/calendar/query/PhabricatorCalendarEventQuery.php:175

    return true;
  }

  protected function loadPage() {
    $events = $this->loadStandardPage($this->newResultObject());

    $viewer = $this->getViewer();
    foreach ($events as $event) {
      $event->applyViewerTimezone($viewer);
    }

    if (!$this->generateGhosts) {
      return $events;
    }

    $raw_limit = $this->getRawResultLimit();
    if (!$raw_limit && !$this->rangeEnd) {
      throw new Exception(
        pht(
          'Event queries which generate ghost events must include either a '.
          'result limit or an end date, because they may otherwise generate '.
          'an infinite number of results. This query has neither.'));
    }

    foreach ($events as $key => $event) {
      $sequence_start = 0;
      $sequence_end = null;
      $end = null;

      $instance_of = $event->getInstanceOfEventPHID();

      if ($instance_of == null && $this->isCancelled !== null) {
        if ($event->getIsCancelled() != $this->isCancelled) {
          unset($events[$key]);
          continue;
        }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Add an explicit result cap: ->setLimit(1000)
  2. Add a range end: ->withDateRange($start_epoch, $end_epoch) (an end bound is required; a start-only range is not enough)
  3. For full exports, loop with paging: keep a limit per page and advance rangeBegin until exhausted

Example fix

// before
$events = id(new PhabricatorCalendarEventQuery())
  ->setViewer($viewer)
  ->setGenerateGhosts(true)
  ->execute();

// after
$events = id(new PhabricatorCalendarEventQuery())
  ->setViewer($viewer)
  ->setGenerateGhosts(true)
  ->withDateRange($start_epoch, $end_epoch)
  ->setLimit(1000)
  ->execute();
Defensive patterns

Strategy: validation

Validate before calling

if ($generate_ghosts && !$limit && !$end_epoch) {
  throw new Exception(
    'Ghost generation requires a result limit or a range end date.');
}
$events = id(new PhabricatorCalendarEventQuery())
  ->setViewer($viewer)
  ->setGenerateGhosts($generate_ghosts)
  ->withDateRange($start_epoch, $end_epoch)
  ->setLimit($limit ?: 1000)
  ->execute();

Try / catch

try {
  $events = $query->execute();
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'infinite number of results') !== false) {
    $query->setLimit(1000);
    $events = $query->execute();
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->setGenerateGhosts(true)->execute() with no setLimit(N) and no withDateRange($start, $end). Even with a rangeBegin only, the query still throws because only rangeEnd counts as the bound.

Common situations: Custom calendar dashboards or export scripts that iterate 'all upcoming instances'; upgrading code that previously worked because a limit was set elsewhere and later removed; search-engine code that forgets to forward the user's rangeEnd filter into the query.

Related errors


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