Since 2026-08-12, Clerk owns auth, organizations, member roles and billing. Supabase Auth is fully retired — nothing calls supabase.auth.*, and auth.uid() errors on Clerk tokens. Supabase is wired to Clerk as a third-party auth provider, which is what lets PostgREST validate Clerk-issued JWTs for RLS.

The sync pipeline

Facts that matter in support:
  • Role source of truth is Clerk’s custom org roles (org:domain_owner, org:approver, org:author, org:viewer; legacy org:admin maps to domain_owner, org:member to viewer). codex.members.role is the synced copy that RLS, RPCs and the worker read. Change roles in Settings → Members (Clerk panel), never in the database.
  • Unknown roles never downgrade anyone: membership updates with unmapped role keys are skipped; membership creation falls back to viewer.
  • Org creation is not webhook-driven. codex.bootstrap_org runs during sign-up (needs the session’s active Clerk org) and creates the org row, the first domain_owner member, an org.created audit row and a “GitHub Actions” API key. Webhook events for Clerk orgs with no codex.orgs row are deliberately ignored.
  • Invites are Clerk’s end to end (email, acceptance, role picked at invite time). The webhook lands the membership; there is no pending_invites table any more.

Role capabilities (application-level)

Reviewer sign-off is a hard gate before approval; the domain owner retains an approve override but cannot skip the reviewed state. Saving a new revision invalidates an existing review.

Entitlements (plan gating)

codex.has_entitlement(org_id, feature) is the single server-side gate, reading webhook-synced org_subscriptions.features. Known feature slugs include github_scanning and llm_extraction (the team plan currently carries seven — the list comes live from Clerk Billing, never a hardcoded map). PR-diff checks (/v1/check) are deliberately not gated — they are the free plan’s core loop. Full-repo scans check the entitlement at execution time, so a downgrade cancels already-queued scans.

Known failure modes

The Supabase side of the Clerk integration is missing. Both halves are required: Supabase Dashboard → Auth → Third-Party Auth must list the Clerk domain, and Clerk’s Supabase integration must be enabled (it adds the role: authenticated claim). This exact pairing broke first sign-in during the migration.
RLS is working — the user has no members row for that org. Check codex.members for their user_… id; if missing, the membership webhook may not have landed (check Clerk webhook delivery logs, endpoint clerk-webhook).
The webhook syncs within seconds normally. Verify the Clerk webhook endpoint is healthy and that the role key is one of the mapped ones — an unmapped key is skipped by design on updates.
Settings → Members & roles, where the Clerk organization panel manages invitations and roles