From d6e697a0c346fb0409ac7206fa04d3402a3371c2 Mon Sep 17 00:00:00 2001 From: inference-bot Date: Tue, 22 Sep 2026 15:50:27 -0600 Subject: [PATCH] Correct OC runbook: email null was a missing email scope, not a regression --- operator-token-runbook.md | 41 ++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/operator-token-runbook.md b/operator-token-runbook.md index 0d9b9b6..efe050d 100644 --- a/operator-token-runbook.md +++ b/operator-token-runbook.md @@ -66,32 +66,29 @@ 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) +## Open Collective: the bot token needs the `email` scope (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. +`... on Individual { email }` inline fragment. -**Failure mode:** the GraphQL `email` field can flip to `null` for *every* -member — including the bot's own `me { email }` — while: +**The `email` scope is load-bearing.** The bot's personal token must be minted +with the `email` scope. Without it, the GraphQL `email` field returns `null` for +*every* member — including the bot's own `me { email }` — even though: -- the bot still authenticates and reports `isAdmin: true`, -- the OC **admin CSV export still shows emails**. +- the bot still authenticates and reports `isAdmin: true`, and +- the OC **admin CSV export still shows emails** (it's a separate, non-GraphQL + path; its `contributorEmail` column is not a GraphQL field). -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. +**Symptoms when the scope is missing:** `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). +A new member's webhook fires cleanly but no "Sent email to …" log line follows, +and no Cloudron user is created. (This is easy to misread as a "guest with no +email" problem — it is not; verify the scope first.) -**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. +**Diagnosis:** run `me { email }` with the bot token. `null` here while +`isAdmin: true` → the token is missing the `email` scope (or the scope was +revoked). Fix: re-add the `email` scope to the token on Open Collective +(Settings → Developers → Personal Tokens). No token rotation, no re-granting of +admin — just the scope.