<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Recurring event exceptions (RECURRENCE-ID / EXDATE) are silently dropped on CalDAV PUT]]></title><description><![CDATA[<h1>Calendar: Recurring event exceptions (RECURRENCE-ID / EXDATE) are silently dropped on CalDAV PUT</h1>
<h2>Summary</h2>
<p dir="auto">When a single occurrence of a recurring event is moved or deleted from a CalDAV client (Apple Calendar on macOS and iOS), the Calendar app accepts the request with a success response but stores only the master event. The exception is lost without any error. Other clients then fetch the event again and fall back to the unmodified series, which also wipes the exception on devices that had shown it before. The published ICS feed never contains any exceptions.</p>
<p dir="auto">This is <strong>silent data loss</strong>: users believe their change was saved, but it only exists locally on the editing device until the next resync.</p>
<h2>Environment</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Cloudron app</td>
<td>Calendar (<code>io.cloudron.calendar</code>) <strong>1.14.2</strong></td>
</tr>
<tr>
<td>Docker image</td>
<td><code>cloudron/io.cloudron.calendar:202611041546350000</code></td>
</tr>
<tr>
<td>Release commit</td>
<td><code>be2a8d35455babde3cc0165113323b5bd1943c78</code></td>
</tr>
<tr>
<td>Clients</td>
<td>Apple Calendar on macOS and iOS (CalDAV account)</td>
</tr>
<tr>
<td>Calendar</td>
<td>Shared calendar, two users with write access</td>
</tr>
</tbody>
</table>
<p dir="auto">The behaviour was already present in 1.13.0.</p>
<h2>Steps to reproduce</h2>
<ol>
<li>In Apple Calendar (CalDAV account on the Calendar app), create a weekly recurring event, e.g. Wednesdays 08:30–17:30.</li>
<li>Move a single occurrence to another day and choose <strong>"Only this event"</strong>.</li>
<li>Check the event on a second device with the same account or a shared user, or open the calendar's published ICS link.</li>
<li>Optionally, edit the same series on the second device, then look at the first device again.</li>
</ol>
<h2>Expected behaviour</h2>
<ul>
<li>The moved occurrence is stored on the server as an override (a VEVENT with <code>RECURRENCE-ID</code>), and a deleted occurrence as <code>EXDATE</code> on the master.</li>
<li>All clients and the ICS feed show the moved or deleted occurrence.</li>
<li>If the server cannot store exceptions, it should <strong>reject</strong> the PUT instead of reporting success.</li>
</ul>
<h2>Actual behaviour</h2>
<ul>
<li>The editing device shows the moved occurrence, but only from its local copy.</li>
<li>All other devices and the ICS feed show the unmodified series.</li>
<li>As soon as the event is saved again from another device, the server issues a new ETag. The first device refetches the event and <strong>its exception disappears as well</strong>.</li>
<li>In our shared calendar the ICS feed contains 53 recurring series and <strong>zero</strong> <code>RECURRENCE-ID</code>, <code>EXDATE</code>, <code>RDATE</code> or <code>STATUS:CANCELLED</code> entries, although several occurrences were moved on the clients.</li>
</ul>
<h2>Root cause (from the source in the image)</h2>
<ol>
<li>
<p dir="auto"><strong>Only the first VEVENT is parsed.</strong> <code>src/ical.js</code>, <code>parseIcsToEvent()</code> (around lines 236–287) is documented as <em>"first VEVENT only"</em> and stops at the first <code>END:VEVENT</code>. Apple Calendar sends the master VEVENT plus one VEVENT per modified occurrence (same <code>UID</code>, with <code>RECURRENCE-ID</code>) in a single resource (RFC 4791 §4.1). Every VEVENT after the first one is ignored.</p>
</li>
<li>
<p dir="auto"><strong>The PUT overwrites the stored event and reports success.</strong> <code>src/events.js</code>, <code>saveEventFromIcs()</code> (around lines 556–597) updates the existing row with the parsed first VEVENT and generates a new ETag. <code>src/routes/caldav.js</code>, <code>handlePut()</code> (around lines 359–428) answers <code>204 No Content</code> with that ETag. The client therefore considers the change saved.</p>
</li>
<li>
<p dir="auto"><strong>No storage for exceptions.</strong> The <code>events</code> table (<code>src/database.js</code>, around lines 90–108) has only <code>recurrence_rule</code>. There is no column or table for overrides or excluded dates.</p>
</li>
<li>
<p dir="auto"><strong>EXDATE / RECURRENCE-ID / RDATE are not handled anywhere.</strong> A search over <code>src/</code> (excluding tests) finds no occurrence of <code>EXDATE</code>, <code>RECURRENCE-ID</code> or <code>RDATE</code>. Deleting a single occurrence (which Apple sends as <code>EXDATE</code> on the master) is lost in the same way.</p>
</li>
</ol>
<p dir="auto">Possible follow-up issue, not observed in practice: because only the first VEVENT is read, a client that puts an override VEVENT before the master would overwrite the series itself with the single occurrence. Apple Calendar happens to send the master first.</p>
<h2>Impact</h2>
<ul>
<li>Moved or deleted occurrences of recurring events are lost for every other user and device, and eventually for the editing device too.</li>
<li>In shared calendars, one user's edit silently reverts the other users' changes.</li>
<li>Anything consuming the ICS feed (dashboards, subscriptions) shows wrong dates.</li>
</ul>
<h2>Suggested fix</h2>
<ul>
<li>Parse all VEVENTs of a resource and store overrides (<code>RECURRENCE-ID</code>) and <code>EXDATE</code> (ideally also <code>RDATE</code>) together with the master event.</li>
<li>Apply them during recurrence expansion and include them in CalDAV GET/REPORT responses and in the ICS export.</li>
<li>Until that is implemented: reject a PUT that contains <code>RECURRENCE-ID</code> overrides or <code>EXDATE</code> (e.g. <code>403</code> with a <code>CALDAV:valid-calendar-object-resource</code> precondition, or <code>415</code>) instead of returning <code>204</code>. Clients would then show a sync error instead of silently losing data.</li>
</ul>
<h2>Workaround</h2>
<p dir="auto">Do not move or delete single occurrences of a series. End the series and create a new one, or use individual events instead.</p>
<hr />
<blockquote>
<p dir="auto"><strong>Disclosure:</strong> I found this problem and made the observations described above myself, on my own devices and with our shared calendar. The source code analysis and the wording of this report were done with the help of an AI assistant.</p>
</blockquote>
]]></description><link>https://forum.cloudron.io/topic/16259/recurring-event-exceptions-recurrence-id-exdate-are-silently-dropped-on-caldav-put</link><generator>RSS for Node</generator><lastBuildDate>Sun, 08 Nov 2026 16:18:31 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/16259.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 06 Nov 2026 08:46:52 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Recurring event exceptions (RECURRENCE-ID / EXDATE) are silently dropped on CalDAV PUT on Sun, 08 Nov 2026 15:42:52 GMT]]></title><description><![CDATA[<p dir="auto">Great news, thanks <a class="plugin-mentions-user plugin-mentions-a" href="/user/nebulon" aria-label="Profile: nebulon">@<bdi>nebulon</bdi></a> !</p>
]]></description><link>https://forum.cloudron.io/post/132575</link><guid isPermaLink="true">https://forum.cloudron.io/post/132575</guid><dc:creator><![CDATA[Kubernetes]]></dc:creator><pubDate>Sun, 08 Nov 2026 15:42:52 GMT</pubDate></item><item><title><![CDATA[Reply to Recurring event exceptions (RECURRENCE-ID / EXDATE) are silently dropped on CalDAV PUT on Sun, 08 Nov 2026 15:03:43 GMT]]></title><description><![CDATA[<p dir="auto">Thanks for your report. Recurring event exceptions should be now supported by the just released new calendar version.</p>
]]></description><link>https://forum.cloudron.io/post/132573</link><guid isPermaLink="true">https://forum.cloudron.io/post/132573</guid><dc:creator><![CDATA[nebulon]]></dc:creator><pubDate>Sun, 08 Nov 2026 15:03:43 GMT</pubDate></item></channel></rss>