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; legacyorg:adminmaps to domain_owner,org:memberto viewer).codex.members.roleis 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_orgruns during sign-up (needs the session’s active Clerk org) and creates the org row, the first domain_owner member, anorg.createdaudit row and a “GitHub Actions” API key. Webhook events for Clerk orgs with nocodex.orgsrow 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_invitestable 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
Sign-in fails with "No suitable key or wrong key type"
Sign-in fails with "No suitable key or wrong key type"
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.User signed in but sees no data / wrong org
User signed in but sees no data / wrong org
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).Role changed in Clerk but the app disagrees
Role changed in Clerk but the app disagrees
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.
Paid but features missing (entitlements empty)
Paid but features missing (entitlements empty)
Check
org_subscriptions for the org: plan_slug, status, features. The
webhook reads feature slugs off the subscription payload (with a Backend API
fallback). An empty features on an active paid plan means the plan in the Clerk
dashboard has no features attached — fix it there, then re-save the subscription
or replay the webhook.

