Google Calendar sync

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.

Released
October 16, 2026
npx skills add timerise-ai/google-calendar-sync
Current release
v0.2.2
Reference docs
14
Non-negotiables
5
License
MIT

What problem does Google Calendar sync solve?

"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.

What does the skill build?

An agent with this skill builds one module for a Next.js App Router app, on your stack. For Google Calendar sync that module consists of:

  • 1. OAuth with PKCE
  • 2. Busy time from the calendars a host chooses kept current by push notifications and make-before-break channel renewal
  • 3. Confirmed bookings written as idempotent events with Google Meet links
  • 4. Derived health per calendar
  • 5. An Express adapter beside the App Router routes

Where do the rules come from?

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` has the record.

The engineering ledger of this skill, for the person editing it. The skill was written by the engineer who owns the calendar integration of a booking API, where hosts connect their calendars to block availability and receive bookings as events, and audited against the earlier implementation of that integration. The earlier implementation had already been hardened once: single-use OAuth state with PKCE, encrypted tokens, HMAC-signed channel tokens bound to their channel, persisted token refreshes, and reconnect clean-up that no longer failed the callback. Those are carried over as they were.

The ledger separates what the audit changed, what was kept on purpose, and what has not run in production yet.

  1. Fixed in the templates

    • A booking's own event blocked the rest of its session Events written for bookings were tagged with the booking id, but busy sync never looked at the tag. On a calendar with both syncs on, the first booking of a group session came back seconds later as a busy block of unlimited quantity, and the engine (which deliberately ignores the host's bookings of the same service) still hid the remaining seats. Shipped: events carrying the private marker are skipped, in busy-sync.md.
    • Renewal stopped the old channel before opening the new one If the new watch call failed, the calendar was left with no channel and no channel record, so the cron never found it again. Busy sync stayed "on" in the UI while its blocks froze. Shipped: new before old, the old kept on failure, and the cron opens a channel for any busy calendar without one, in channels-and-settings.md.
    • Busy time froze on quiet calendars The window was [now, now + 180 days], but it only moved when a notification arrived. A calendar nobody edited for a month never imported events entering the window, and missed notifications were never repaired. Shipped: the cron resyncs every busy calendar.
    • Concurrent notifications left phantom busy blocks Busy blocks had random ids. Two syncs started by one burst both saw an event as new and both created a block. The next sync keyed blocks by event id in a map, so one duplicate was updated and deleted with the event while the other stayed forever, blocking time the host had freed. Shipped: deterministic ids, a per-calendar lease with a queued rerun, and a diff that deletes every stored row not desired.
    • Multi-day all-day events blocked only their first day All-day events were mapped from start.date to 23:59:59.999 of the same day; end.date was ignored. A week's vacation blocked Monday. Shipped: start.date to the exclusive end.date, at local midnights of the calendar's zone, correct on DST days.
    Show 9 more
    • Only the first 2500 events were read events.list was called once with maxResults: 2500; nextPageToken was ignored, and the diff then deleted the blocks of every event past the first page. Shipped: every page, and any failure aborts before deleting, in google-api.md.
    • Free and declined events blocked Events marked Free (most all-day events by default) and invitations the host declined were imported as busy. Shipped: both skipped, except on holiday calendars.
    • Shared calendars could not receive bookings, and new rows looked read-only Only owner counted as writable, though writer can insert events. Separately, a calendar's first listing stored isWritable: false regardless of its role, so the booking-sync toggle was unavailable until the list was loaded a second time. Shipped: owner or writer, correct on first listing, and a server-side guard on enabling booking sync.
    • A revoked grant looked like "no calendars" Every listing error returned an empty list, nothing recorded invalid_grant, and renewals failed twice a day in the logs only. Shipped: reauth_required on the connection, surfaced through health and the list route.
    • Reconnecting wiped every setting Re-authorizing (the fix for a revoked grant) deleted all calendar rows and busy blocks, even for the same Google account. Shipped: the account e-mail identifies the account; the same account keeps its rows and gets fresh channels, a different one is torn down.
    • Booking events were not idempotent Events got Google-generated ids. A repeated trigger, or the backfill when booking sync was switched on (which de-duplicated only against the 180-day window), created a second event. Shipped: the event id is derived from the booking id; 409 becomes an update, in booking-events.md.
    • Smaller ones could push it past Google's 256-character limit. It now holds only the channel id.
    • The second Next.js route did not compile on its own Found by the 0.2.0 release check, not by the audit. app/api/calendar/google/calendars/[calendarId]/route.ts shared a code block with the [action] route and used calendarRoutes without importing it, so the file an agent copied failed tsc. Shipped: its own block with its own import, in http-routes.md; the release check writes every block to its own path and compiles it.
    • The wiring failed next build without secrets Found by the 0.2.0 agent evals, not by the audit. setup.ts read every variable at import, and next build imports each route module to collect page data, so a build without the secrets (CI, a preview deployment) failed with CALENDAR_... is not set; every agent wrapped it in a lazy getter of its own. Shipped: calendarRoutes() and calendarDeps() build on first call, in http-routes.md; the release check runs next build with an empty environment.
Read the full record in provenance.md ›

What are the 5 rules the module never breaks?

Every module built from this skill holds these, whoever builds it. The same list is in the skill's README and SKILL.md, so the agent reads it before it writes a line.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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 shows.

When should you use it, and when not?

Use it for

  • 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.

Not for

  • Outlook and Microsoft 365 calendarsInsteadmicrosoft-calendar-sync
  • Generating bookable slots and subtracting busy timeInsteadtime-slot-generator
  • Public booking pages with Google and Outlook sync built inInsteadcalendar-scheduling-links
  • A calendar UI or invitation e-mailsInsteadThe host's own UI and e-mail
  • Availability from a whole Workspace without per-user OAuthInsteadGoogle's free/busy query with domain-wide delegation, not templated here

How do I install it?

One command. The skills.sh CLI installs the skill into every skills-compatible agent it finds.

$ npx skills add timerise-ai/google-calendar-sync

Claude Code

Invoke with /google-calendar-sync

Codex CLI

Invoke with $google-calendar-sync

Gemini CLI

Invoke with /skills

Name the agents instead with -a, for example npx skills add timerise-ai/google-calendar-sync -a claude-code -a codex. Or clone the repository into your agent's skills folder. Nothing in it is agent-specific.

What is inside the repository (17 entries)
  • SKILL.mdEntry point: trigger, architecture, adaptation contract, critical facts, hard rules, quick start, reference directory
  • references/adaptation.mdHost prerequisites, seam contract, order of work, the non-negotiables
  • references/data-model.mdRecords, types, the store contract, the rename table
  • references/oauth-and-tokens.mdGoogle Cloud setup, scopes, the OAuth round trip, token lifetime, encryption
  • references/google-api.mdThe fetch-based client and the API facts that shape it
  • references/busy-sync.mdWhich events block, the sync run, feeding availability
  • references/booking-events.mdWriting and deleting booking events, Meet links
  • references/channels-and-settings.mdWatch channels, renewal, settings transitions, the calendar list
  • references/http-routes.mdRoute handlers, the webhook, Next.js and Express wiring
  • references/firestore-backend.mdCollections, index, rules, store
  • references/supabase-backend.mdSchema, RLS, optimistic concurrency, store
  • references/operations.mdHealth, logs, configuration, runbook
  • references/testing.mdRunning the suite, what it pins, mutation check, doubles
  • references/test-suite.mdThe 32 tests
  • references/provenance.mdThe audit ledger: fixed, kept deliberately, changed, designed here
  • evals/prompts.md with what an operator types, and one file per agent eval run
  • .github/workflows/agent-eval.ymlRuns the agent evals on every published release

Recent releases

  1. v0.2.2October 16, 2026

    Wording release, from scoring the prompt-1 agent eval runs against 0.2.1.

  2. v0.2.1October 16, 2026

    Fix release, from scoring the prompt-1 agent eval runs against 0.2.0.

  3. v0.2.0October 16, 2026

    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.

What do I tell my agent?

Say what you need in your own words; the skill supplies the how. These are starting points, and the ones we tested say how it went.

  1. 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.

  2. 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.

    Firestore
  3. 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.

How does it do in each agent?

We install the skill into an empty Next.js app, give the agent one of the prompts above and no further help, then type-check, build and run the tests it left behind. Nothing is fixed by hand before the checks, and a failing run is published like a passing one. The procedure and every result are public, and the first prompt runs again before each release.

  • Gemini CLI0.64.0

    gemini-3.8-flash

    Built, checks pass
    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.
    Typecheck: passBuild: passTests: pass
    Time
    11 min
    Changed
    28 files, +3,349 lines
    Stack
    Supabase
    Skill
    v0.2.2
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8. Scored from the summary only; item 2 is held on its word. setup.ts builds on first call and checks the session with auth.getUser, the cron route answers GET and POST as shipped, the suite runs 32 tests in each of four zones, and the final message ends with all six handover items, naming the two placeholder seams and the booking hooks to call.

  • Codex CLIcodex-cli 0.163.0

    gpt-6.1-sol

    Built, checks pass
    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.
    Typecheck: passBuild: passTests: pass
    Time
    3 min
    Changed
    37 files, +4,145 lines
    Stack
    Supabase
    Skill
    v0.2.2
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8. Scored from the transcript. It installed @supabase/supabase-js and @supabase/ssr from the registry, extracted every block verbatim and derived setup.ts from the shipped one by swapping the store, the host seams and defer. The suite runs unchanged in four zones, 32 each; it also ran the ten mutations of the testing table against temporary copies, all detected, leaving the templates untouched. Supabase sessions are verified, .env.example lists the eight variables, the cron is 12-hourly, and the handover has all six items, the sixth saying no placeholder seam remains.

  • Claude Code2.1.301

    claude-opus-5-5

    Built, checks pass
    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.
    Typecheck: passBuild: passTests: pass
    Time
    5 min
    Changed
    31 files, +3,674 lines
    Stack
    Supabase
    Skill
    v0.2.2
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8. Scored from the summary. setup.ts builds on first use and the build passes with no variables set; the host seams sit in calendar-sync/host.ts beside it, with listUpcomingBookingsForHost and saveMeetingUrl left as named placeholders rather than an invented bookings table. authenticate checks the Supabase session or Bearer token with Supabase, the suite runs as shipped (32 in four zones), the cron is 12-hourly in vercel.json, and the final message ends with all six handover items, the placeholders among them.

How long does it take, and what does it cost?

We quote this module per project. The price depends on what it has to connect to. The path to a number is short and free:

  1. Step 1

    Brief

    Tell us what the module must connect to. Takes minutes, in a chat.

  2. Step 2

    Prototype in 48 hours

    A clickable prototype of your system and a quote, at no cost.

  3. Step 3

    Build and handoff

    One project price. Source code, documentation and IP are yours.

Two ways to get Google Calendar sync

Build it yourself

Install the skill. Your own agent builds the module.

  • MIT licensed, no strings
  • Runs in Claude Code, Codex CLI and Gemini CLI
  • The same rules our engineers build by
$ npx skills add timerise-ai/google-calendar-sync

Build it with Timerise

Send a brief. We build Google Calendar sync into a system you own.

  • Clickable prototype and a quote within 48 hours, free
  • One project price, no subscription, no commission
  • Source code, documentation and IP handed over
3 years of support included.

Generated from the skill's own files at commit 579089c. Every rule above links to where the repository says it. All skills