Google Calendar Cleanup¶
Alaners' Google Calendars accumulate toil: events nobody attends, recurring meetings that outlive their purpose, and "forever" series with no end date. The calendar cleanup feature trims this automatically, without spamming attendees or forcing constant re-scheduling.
It applies three rules to each active alaner's primary calendar, acting only
on events the alaner organizes (organizer.self). All summaries are posted to
#calendar-lifecycle-cleanup. Two of the rules run inside the existing daily
run_tasks sweep — no new cron.
Scope
Only AlanAccountType.alaner accounts that are is_active are touched.
External users and service accounts are never in scope.
The three rules¶
| Rule | Mechanism | What it does | Window / cap |
|---|---|---|---|
| 1 | GoogleCalendarTerminateFullyDeclinedEvents (daily task) |
Deletes events where every human attendee has declined. Series are expanded (single_events=True), so only the declined instances are removed — never the whole series. |
[now - 7d, now + 30d] |
| 3 | GoogleCalendarCapOpenEndedRecurring (daily task) |
Caps recurring meetings that lack UNTIL/COUNT at created_at + 3 months (floored to now + 1d if that is already past). Idempotent — already-bounded series are skipped. |
[now - 7d, now + 30d] |
| 2 | expire_recurring_meetings --until=YYYY-MM-DD (one-off CLI) |
Caps every currently-recurring meeting at a chosen date. RQ-enqueued per alaner. Skips series already bounded on or before the target. | user-chosen --until |
Rule 1 — terminate fully-declined events¶
GoogleCalendarTerminateFullyDeclinedEvents gates in should_run (active alaner
only) and acts in run. all_human_attendees_declined ignores resource
attendees and the organizer; a solo event (organizer only) is never deleted.
Deletion uses delete_calendar_event on the "primary" calendar.
Rule 3 — cap open-ended recurring meetings¶
GoogleCalendarCapOpenEndedRecurring skips non-recurring events and events whose
RRULE already carries UNTIL or COUNT. It reads the event's created
timestamp; events without one are skipped and counted (reported as a :warning:
line). The new UNTIL is written with cap_rrule_until +
patch_calendar_event_recurrence.
Rule 2 — force-cap all recurring meetings (manual)¶
A one-off Click command for a deliberate, org-wide cap at a chosen date (e.g.
end of a season). It only touches series whose UNTIL is missing or strictly
later than --until; earlier bounds are left alone.
How calendars are accessed¶
Tasks and the CLI resolve a client with
get_calendar_service(impersonated_as=<alaner email>) — a Google service account
using domain-wide delegation (credentials.with_subject), so "primary" and
organizer.self resolve to that alaner's own calendar.
The two daily tasks run in the same per-user job and share one API fetch via
list_organized_events in providers/google_calendar_lifecycle_logic.py, cached
with @cached_for(minutes=10, local_ram_cache_only=True, no_serialization=True).
Rule 1 (single_events=True) and rule 3 (single_events=False) cache separately.
Recurrence edits go through patch_calendar_event_recurrence
(shared/services/google/calendar.py) with sendUpdates="none" — no
notification emails are sent to attendees when a cap is applied.
Operating it¶
- Enable the daily tasks. They are inert until a
UserLifecycleTaskStatusrow exists and is active. After merge, runsync_task_statusto create rows forgoogle-calendar-terminate-fully-declined-eventsandgoogle-calendar-cap-open-ended-recurring, then toggle them active via the admin / Notion surface:
- Dry-run first, scoped to just these tasks for one alaner (logs "Would delete/cap …", writes nothing):
Once active, they run automatically in the daily run_tasks sweep.
- Run the one-off cap (rule 2).
--untilmust be a future date (elseUsageError); omit--alaner-idto sweep all active alaners. Work runs on theLOW_PRIORITYRQ queue.
Reporting. Per-task start/stop summaries go to #calendar-lifecycle-cleanup;
CLI and task failures go to #alan_home_alerts. Like any UserLifecycleTask, a
task auto-deactivates after N consecutive failures — see the framework behavior in
the User Lifecycle overview.
Key files¶
| File | Contents |
|---|---|
apps/eu_tools/user_lifecycle/providers/google_calendar_lifecycle.py |
Rule 1 & 3 task classes + rule 2 worker (expire_recurring_meetings_for_alaner, process_expire_recurring_meetings_for_alaner) |
apps/eu_tools/user_lifecycle/providers/google_calendar_lifecycle_logic.py |
Pure helpers: cached event listing, RRULE parse/cap, declined-attendee check |
apps/eu_tools/user_lifecycle/scheduled_tasks.py |
Provider import (auto-registers the tasks) + expire_recurring_meetings CLI command |
shared/services/google/calendar.py |
patch_calendar_event_recurrence (silent recurrence patch) |