Bug Description
Events deleted in Google Calendar keep showing up in Twenty and never go away on their own. It mainly affects connected accounts that haven't synced in a while (a week or more) or accounts that were reconnected after a pause. The calendar channel's sync status shows ACTIVE / completed with no errors, so nothing in the UI hints at a problem.
Expected behavior
An event deleted on Google's side should eventually be removed from Twenty, even if Twenty's sync was stale or the account was reconnected after a pause.
Technical inputs
Root cause is two compounding bugs in the Google Calendar sync driver (GoogleCalendarGetEventsService / CalendarFetchEventsService):
-
HTTP 410 from Google is silently swallowed. Google Calendar's events.list returns 410 fullSyncRequired when a syncToken has expired or been invalidated - which is exactly what happens after a long sync gap or a disconnect/reconnect. GoogleCalendarGetEventsService.handleError special-cased status === 410 to not throw, so the caller received a fake "successful" empty page ({ calendarEventIds: [], calendarEventIdsToDelete: [], nextSyncCursor: '' }) instead of an error. That's why the sync status shows completed with no errors - the failure is never surfaced, and no deletions are ever applied for that round. (Twenty's Microsoft Calendar driver already maps its own 410 to a SYNC_CURSOR_ERROR that triggers a proper full resync - the Google driver never produced that exception.)
-
A full resync never reconciles deletions either. Once a full sync runs (events.list without a syncToken), Google's response only contains events that currently exist - it does not return a cancelled tombstone for something deleted during the gap. The only place in the pipeline that ever marks an event for deletion is item.status === 'cancelled', which is only ever populated by an incremental sync. So even a full resync that runs correctly never notices that a locally-stored event no longer exists on Google's side, and it stays in Twenty indefinitely.
Verification
I verified both of these directly against a live Google Calendar API surface using Veris - a contract-accurate sandbox for Google Calendar/Google identity - through Veris's veris-proxy, which reroutes the actual googleapis client's real https://www.googleapis.com traffic into the sandbox at the kernel level, so the driver code under test is exercised completely unmodified, with its real production hostnames and code paths:
- Created events, ran an initial full sync, deleted one event, then invalidated the sync token the driver would replay - confirmed Google returns
410 with reason: "fullSyncRequired", exactly as assumed.
- Confirmed an incremental sync (still-valid
syncToken) does return the deleted event with status: "cancelled" - the tombstone mechanism the whole pipeline relies on.
- Confirmed a full sync (no
syncToken) does not return the deleted event at all, in any form - it's just silently absent.
- Ran the actual unmodified
GoogleCalendarGetEventsService class through the sandbox: against the current code it resolves successfully on a 410 instead of throwing (the bug); a fix that maps 410 to the existing SYNC_CURSOR_ERROR exception (matching the Microsoft driver's pattern) makes it throw correctly, and veris-proxy's own receipt confirmed the sandbox genuinely received the traffic.
I'll open a PR shortly with the fix (map 410 to SYNC_CURSOR_ERROR, and reconcile a full sync's results against existing local associations) plus regression tests.
Bug Description
Events deleted in Google Calendar keep showing up in Twenty and never go away on their own. It mainly affects connected accounts that haven't synced in a while (a week or more) or accounts that were reconnected after a pause. The calendar channel's sync status shows
ACTIVE/ completed with no errors, so nothing in the UI hints at a problem.Expected behavior
An event deleted on Google's side should eventually be removed from Twenty, even if Twenty's sync was stale or the account was reconnected after a pause.
Technical inputs
Root cause is two compounding bugs in the Google Calendar sync driver (
GoogleCalendarGetEventsService/CalendarFetchEventsService):HTTP 410 from Google is silently swallowed. Google Calendar's
events.listreturns410 fullSyncRequiredwhen asyncTokenhas expired or been invalidated - which is exactly what happens after a long sync gap or a disconnect/reconnect.GoogleCalendarGetEventsService.handleErrorspecial-casedstatus === 410to not throw, so the caller received a fake "successful" empty page ({ calendarEventIds: [], calendarEventIdsToDelete: [], nextSyncCursor: '' }) instead of an error. That's why the sync status shows completed with no errors - the failure is never surfaced, and no deletions are ever applied for that round. (Twenty's Microsoft Calendar driver already maps its own 410 to aSYNC_CURSOR_ERRORthat triggers a proper full resync - the Google driver never produced that exception.)A full resync never reconciles deletions either. Once a full sync runs (
events.listwithout asyncToken), Google's response only contains events that currently exist - it does not return a cancelled tombstone for something deleted during the gap. The only place in the pipeline that ever marks an event for deletion isitem.status === 'cancelled', which is only ever populated by an incremental sync. So even a full resync that runs correctly never notices that a locally-stored event no longer exists on Google's side, and it stays in Twenty indefinitely.Verification
I verified both of these directly against a live Google Calendar API surface using Veris - a contract-accurate sandbox for Google Calendar/Google identity - through Veris's
veris-proxy, which reroutes the actualgoogleapisclient's realhttps://www.googleapis.comtraffic into the sandbox at the kernel level, so the driver code under test is exercised completely unmodified, with its real production hostnames and code paths:410withreason: "fullSyncRequired", exactly as assumed.syncToken) does return the deleted event withstatus: "cancelled"- the tombstone mechanism the whole pipeline relies on.syncToken) does not return the deleted event at all, in any form - it's just silently absent.GoogleCalendarGetEventsServiceclass through the sandbox: against the current code it resolves successfully on a410instead of throwing (the bug); a fix that maps410to the existingSYNC_CURSOR_ERRORexception (matching the Microsoft driver's pattern) makes it throw correctly, andveris-proxy's own receipt confirmed the sandbox genuinely received the traffic.I'll open a PR shortly with the fix (map 410 to
SYNC_CURSOR_ERROR, and reconcile a full sync's results against existing local associations) plus regression tests.