phacility/phabricator · error · Exception

Unable to write ICS document: event has no UID, but each eve

Error message

Unable to write ICS document: event has no UID, but each event MUST have a UID.

What it means

PhutilICSWriter refuses to serialize a PhutilCalendarEventNode that has an empty UID. RFC5545 requires a UID property on every VEVENT (it is the identity used by clients to correlate events across updates), so the writer fails fast rather than emitting an invalid ICS document.

Source

Thrown at src/applications/calendar/parser/ics/PhutilICSWriter.php:145

      '2.0');

    $properties[] = $this->newTextProperty(
      'PRODID',
      self::getICSPRODID());

    return $properties;
  }

  public static function getICSPRODID() {
    return '-//Phacility//Phabricator//EN';
  }

  private function getEventNodeProperties(PhutilCalendarEventNode $event) {
    $properties = array();

    $uid = $event->getUID();
    if (!strlen($uid)) {
      throw new Exception(
        pht(
          'Unable to write ICS document: event has no UID, but each event '.
          'MUST have a UID.'));
    }
    $properties[] = $this->newTextProperty(
      'UID',
      $uid);

    $created = $event->getCreatedDateTime();
    if ($created) {
      $properties[] = $this->newDateTimeProperty(
        'CREATED',
        $event->getCreatedDateTime());
    }

    $dtstamp = $event->getModifiedDateTime();
    if (!$dtstamp) {
      throw new Exception(

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Always set a UID before export: $event->setUID(...)
  2. Generate one when the source lacks it, e.g. Filesystem::readRandomCharacters(12) or a PHID-derived value
  3. Validate the whole node (UID present) in a shared export helper so every code path is covered

Example fix

// before
$event = PhutilCalendarEventNode::newFromDictionary($dict);
$ics = PhutilICSWriter::writeDocuments(array($event));

// after
$event = PhutilCalendarEventNode::newFromDictionary($dict);
if (!strlen($event->getUID())) {
  $event->setUID(Filesystem::readRandomCharacters(12));
}
$ics = PhutilICSWriter::writeDocuments(array($event));
Defensive patterns

Strategy: validation

Validate before calling

foreach ($events as $event) {
  if (!strlen($event->getUID())) {
    throw new Exception(
      pht('Refusing to export event without a UID: %s', $event->getName()));
  }
}
$ics = PhutilICSWriter::writeDocuments($events);

Try / catch

try {
  $ics = PhutilICSWriter::writeDocuments(array($event));
} catch (Exception $ex) {
  // Enrich the failure with which event lacked the UID before rethrowing.
  throw new Exception('Event '.$event->getPHID().' failed ICS export: '.$ex->getMessage());
}

Prevention

When it happens

Trigger: Calling PhutilICSWriter::writeDocuments(array($event)) on an event node built with newFromDictionary() or by hand where setUID() was never called, or where the source data (e.g. a foreign calendar row) has a null/empty UID column.

Common situations: Exporting events ingested from systems that do not guarantee UIDs; custom event-node builders in tests or migration scripts that skip identity fields; exporting freshly constructed stub events.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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