# Google Calendar sync (Agent Skill)

> Two-way Google Calendar sync: hosts connect their account, their events block availability, kept current by push notifications, and confirmed bookings land in their calendar with Google Meet links.

Page: https://timerise.ai/skills/google-calendar-sync

Agent Skill `google-calendar-sync`: https://github.com/timerise-ai/google-calendar-sync. Category: Scheduling. Runs on Next.js; backend: Firestore or Supabase; storage, auth and the booking source behind seams.

Latest release: v0.2.2 (2026-10-16).

Install: npx skills add timerise-ai/google-calendar-sync. Timerise builds it on a brief; the price comes from the brief.

"Connect your Google Calendar" is one button on a settings page. Behind it, a booking system has to know, at the moment a client picks a time, that the host has a dentist appointment, and it has to put the new booking into the host's calendar so nobody books over it from the other side.

The button is the easy part. The hard part is what happens when Google sends five notifications for one change, when a calendar has more events than one page holds, when an all-day holiday falls on a different day in the server's time zone, when the watch channel expires on a Saturday night, or when the host revokes access and nobody notices. Get one of those wrong and the calendar shows a host free while they sit in another meeting, or busy all week for no reason.

This skill gives your coding agent the whole sync, written by the engineers who build calendar integrations for booking systems: the OAuth connect, busy time from the calendars a host chooses, push notifications with channel renewal, booking events with Google Meet links, and health per calendar so a broken connection becomes a reconnect prompt. You own the code, run it on Next.js on Vercel, with Supabase or Firestore, under your own Google Cloud project.

Build two-way Google Calendar sync for a booking or scheduling app: hosts connect their Google account with OAuth, events on the calendars they choose block their availability, kept current by push notifications and a renewal cron, and confirmed bookings are written to their calendar as events, with Google Meet links when asked. Use when: (1) an app with bookings, appointments, classes or shifts must respect staff's Google Calendar busy time or put bookings into it, (2) an existing calendar sync shows phantom busy time, missed or duplicate events, group sessions blocked after the first booking, sync that stopped without an error, or users stuck after revoking access, (3) the user mentions: Google Calendar integration, two-way calendar sync, connect Google Calendar, busy time, free/busy, events.watch, push notifications, X-Goog-Channel-Token, channel renewal, calendar.events scope, refresh token, invalid_grant, offline access, PKCE, Google Meet link, conferenceData, extendedProperties, hangoutLink. Carries a dependency-free Google client on fetch, a pure busy-time mapper and diff, single-flight sync, idempotent booking events, make-before-break channel renewal, derived health per calendar, Firestore and Supabase stores, and a 32-test node:test suite run under four process time zones. Next.js App Router route handlers, with an Express adapter for other Node servers; storage, auth and the booking source are seams. Not Outlook or Microsoft 365 (microsoft-calendar-sync), not slot generation (time-slot-generator), not a calendar UI, not ICS feeds.

This skill was written by the engineer who owns the calendar integration of a booking API, and audited against the earlier implementation of that integration together with the slot engine that consumes its output. The templates hold the properties a calendar sync has to hold: a booking's own event never becomes busy time; a burst of notifications converges to one block per event; every page of a calendar is read, and a failed page deletes nothing; a multi-day all-day event blocks every day, whatever the server's time zone; a channel is renewed before the old one is stopped; a booking pushed twice is one event; revoked access becomes a reconnect prompt that keeps every setting. 32 tests state them, run under four process time zones, and [`references/provenance.md`](references/provenance.md) has the record.

What it builds:
- OAuth with PKCE
- Busy time from the calendars a host chooses kept current by push notifications and make-before-break channel renewal
- Confirmed bookings written as idempotent events with Google Meet links
- Derived health per calendar
- An Express adapter beside the App Router routes

When to use:
- Hosts connect Google Calendar and pick calendars that block their availability.
- Bookings appear in the host's Google Calendar, optionally with a Meet link, and vanish on cancel.
- Auditing an existing Google sync: duplicates, ghosts, sync that stopped, reconnect loops.
- Moving that sync between Firestore and Supabase/Postgres.

When not to use:
- **Generating bookable slots, subtracting busy time**: `time-slot-generator`. This skill *produces* the busy blocks it consumes.
- **Outlook or Microsoft 365**: `microsoft-calendar-sync`, the same architecture and record shapes on Microsoft Graph.
- **Showing a calendar UI or sending invitations**: the host's own e-mail; this writes events without Google's invitation e-mails.
- **Read-only availability without per-user OAuth**: Google's free/busy query with a service account and domain-wide delegation is a different design.

Non-negotiables:
- Never take the user id from the OAuth callback URL. The state is a key to a server-side, single-use, ten-minute record; otherwise anyone attaches their calendar to someone else's account. The connect tests consume a state twice, let one expire, and check the user comes from the server.
- Never trust a notification before its channel token verifies against the channel id it arrived on. The endpoint is public; the token is the only credential. The webhook test sends a forged token, a foreign resource and an unknown channel.
- Never stop the old channel before the new one exists. If the new watch fails, the calendar has no channel and its busy time stops updating while the UI says "on". The renewal tests fail the new watch and expect the old channel kept, and remove a channel and expect the cron to open one.
- Never store tokens in clear text. A refresh token is long-lived read and write access to a calendar. The token test round-trips AES-256-GCM and rejects tampering; the connect test reads the stored tokens back encrypted.
- Never let a Google failure fail a booking. Outbound writes log and return; the booking stands. `pushBookingEvent` and `removeBookingEvent` catch every Google error and return a result per calendar, as [booking-events.md](references/booking-events.md) shows.

Not this:
- Outlook and Microsoft 365 calendars: `microsoft-calendar-sync`
- Generating bookable slots and subtracting busy time: `time-slot-generator`
- Public booking pages with Google and Outlook sync built in: `calendar-scheduling-links`
- A calendar UI or invitation e-mails: The host's own UI and e-mail
- Availability from a whole Workspace without per-user OAuth: Google's free/busy query with domain-wide delegation, not templated here

What to tell your agent:
- Our staff should be able to connect their Google Calendar in this Next.js app. Events on the calendars they pick must block their availability and stay up to date without anyone pressing refresh, and every confirmed booking should land in their calendar, with a Google Meet link if they want one. We use Supabase.
- Add Google Calendar sync for our instructors on Firestore. We run group classes: when a student books a class it goes into the instructor's calendar, but the class must stay open for the other students.
- Audit our Google Calendar integration: hosts show as busy at times they cleared weeks ago, some calendars stopped updating without any error, and people who revoked access just see an empty calendar list.

Agent eval results (newest per agent and prompt):
- Gemini CLI (gemini-3.8-flash) on v0.2.2, 2026-10-16: pass (Supabase)
- Codex CLI (gpt-6.1-sol) on v0.2.2, 2026-10-16: pass (Supabase)
- Claude Code (claude-opus-5-5) on v0.2.2, 2026-10-16: pass (Supabase)

Recent releases:
- v0.2.2 (2026-10-16): Wording release, from scoring the prompt-1 agent eval runs against 0.2.1.
- v0.2.1 (2026-10-16): Fix release, from scoring the prompt-1 agent eval runs against 0.2.0.
- v0.2.0 (2026-10-16): The skill brought to the Timerise skill standard, with a new adaptation reference, agent evals, and one template fix found by compiling every block on its own.
