diff --git a/operator-token-runbook.md b/operator-token-runbook.md index a66c4d4..0d9b9b6 100644 --- a/operator-token-runbook.md +++ b/operator-token-runbook.md @@ -65,3 +65,33 @@ Env vars for custom apps are set via: - Cloudron CLI: `npm install -g cloudron` (install on your workstation, not the server), then `cloudron login my.inference.coop`. - Cloudron API docs: `docs.cloudron.io/api/`. + +## Open Collective email visibility can regress (Sept 2026) + +The portal identifies members by email, read via the bot's OC personal token +(`OC_PERSONAL_TOKEN`) as `collective.members.nodes[].account.email` with the +`... on Individual { email }` inline fragment. This is normally populated +because the bot is an **admin** of the collective. + +**Failure mode:** the GraphQL `email` field can flip to `null` for *every* +member — including the bot's own `me { email }` — while: + +- the bot still authenticates and reports `isAdmin: true`, +- the OC **admin CSV export still shows emails**. + +When this happens, `fetch_members()` filters out empty emails and returns `[]`, +so the webhook can't provision anyone by email and the nightly sweep goes into +its empty-list fail-safe (skips, doesn't deactivate). Symptoms: a new member's +webhook fires cleanly but no "Sent email to …" log line follows, and no +Cloudron user is created. + +**Diagnosis:** run `me { email }` with the bot token. `null` here (while +`isAdmin: true`) confirms the regression is OC-side in the GraphQL layer, *not* +a token rotation and *not* a lost admin grant. The CSV is the independent +ground-truth check. + +**Interim workaround:** for any member who can't be provisioned by email, get +their address another way and onboard via the manual path — add to +`MANUAL_MEMBERS`, then `POST /admin/provision` (X-Admin-Token header). The +guest-with-no-email case is *not* the cause; verify with `me { email }` before +concluding a member is unreachable.