# Calendar OAuth Disconnect RCA — alex@remark.re / Sloane (2026-06-26)

Published: 2026-06-30
Updated: 2026-06-30
Source: https://docs.oppy.pro/n/calendar-oauth-revoke-rca-1782838715
Account: Oppy Inc

---

# Calendar OAuth Disconnect — Root Cause Analysis (2026-06-26)

**Affected:** Sloane appointment scheduling (Oppy Inc), `alex@remark.re` + `ag@oppy.pro` calendars, and the comms-api ManagedCalendars connected via the same grant.

## Summary
On 2026-06-26 a `revoke_access` (Google Calendar disconnect) fired for oppy `User #2` (`alex@remark.re`). It POSTed to Google&#39;s token-revoke endpoint — actually revoking the grant at Google — then nulled `google_calendar_refresh_token` and `calendar_id` locally. Because **oppy and comms-api share the same Google OAuth client** (`GOOGLE_CALENDAR_CLIENT_ID = 883356951770-qkh…`), revoking at Google simultaneously killed comms-api&#39;s calendars connected via `alex@remark.re` — including **`ag@oppy.pro`, Sloane&#39;s primary appointment calendar**. One revoke → both apps dark → the appointment schedule stopped working.

## The broken record (oppy `users`, plaintext columns)
User #2 (auth `alex@remark.re`), updated 2026-06-26 17:03 UTC:
- `google_calendar_refresh_token` = **nil** (wiped)
- `calendar_id` = **nil** (wiped)
- `google_calendar` = **true** (still claims connected)
- `google_sync_token` = present (stale leftover)

## Mechanism (confirmed in code)
The only oppy path that nulls that column is `revoke_access` in `app/controllers/api/v{2,3}/scheduler/google_calendars_controller.rb`:
- POST `https://oauth2.googleapis.com/revoke`, then on success: `current_user.update(google_calendar_refresh_token: nil, calendar_id: nil)`
- **Bug:** it does NOT set `google_calendar = false`, so the account still shows &quot;connected&quot; with no token → silent failure, no reconnect prompt.

## Cross-app cascade (oppy ↔ comms-api)
Same OAuth client id across both apps (verified). comms-api&#39;s 16 ManagedCalendars connected via `alex@remark.re` (incl. `ag@oppy.pro`/Sloane, `spencer.g`, `kaitlyn.s`, `ana.p`, `michael.p@remarkvisions.com`, + group calendars) flipped to `status=token_expired` in the same window because Google revoked the shared grant.

## Recovery
The refresh token was **revoked at Google**, not just deleted locally — so no valid copy exists in any database (comms-api&#39;s is dead too, same grant). **Fix = re-authorize:** reconnect Google Calendar in oppy (re-consent → fresh refresh token + re-select `calendar_id`). That restores the appointment schedule and Sloane.

## How widespread
- **oppy:** of 313 users with `google_calendar=true`, only 3 have a token; **310 are tokenless** — but a long-standing accumulation (scattered across 2023; old disconnects). **Only User #2 broke on 06-26** — recent + isolated.
- **comms-api:** 178/213 ManagedCalendars connected; 35 `token_expired` across 8 accounts (grant-specific, not platform-wide).

## Engineering follow-ups
1. `revoke_access` must set `google_calendar=false` (and surface &quot;reconnect&quot;) — fixes the silent-broken state behind the 310 stale flags.
2. Document/guard the oppy↔comms-api shared-OAuth-client coupling: a disconnect in one app revokes calendars in both.
3. Data cleanup: reconcile the 310 `google_calendar=true` + no-token users.
4. Proactive alerting: 7 accounts are fully calendar-dark with no notification.

## Verification status
DB facts (nulled token, dates, counts, code path, shared client id) are **confirmed**. **Inferred** (strong, consistent): the 06-26 nulling was a `revoke_access` call that cascaded to comms-api via the shared grant. **Unknown:** what triggered the revoke (UI click / reconnect-gone-wrong / automated) — recommend a Cloud Logging check for the disconnect-endpoint hit on User #2 on 06-26.

