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
- Always set a UID before export: $event->setUID(...)
- Generate one when the source lacks it, e.g. Filesystem::readRandomCharacters(12) or a PHID-derived value
- 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
- Make UID assignment part of the canonical event-node builder, not each export site
- Generate and persist a UID at ingestion time for foreign events lacking one
- Add a pre-export validator that checks UID and modified-time together
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
- Unable to write ICS document: event has no modified time, bu
- RRULE BYDAY value "%s" has an offset with magnitude "%s", bu
- RRULE specifies BYMONTHDAY with FREQ set to WEEKLY, which vi
- RRULE specifies BYYEARDAY with FREQ of DAILY, WEEKLY or MONT
- Value "%s" in RRULE "%s" parameter is invalid: it must be be
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/8db6dfe9cf2aca90.
Report an issue: GitHub.