Si ton projet Supabase affiche infinite recursion detected in policy for relation "profiles", tu n'es pas seul. C'est l'une des erreurs les plus fréquentes en vibe coding, et elle bloque le chargement complet de l'application.
Pourquoi cette erreur se produit
Supabase utilise Row Level Security (RLS) pour contrôler qui peut lire quelle donnée. Le problème apparaît quand une politique RLS sur une table fait référence à la même table — créant une boucle infinie.
Exemple typique : tu as une table profiles et une politique qui vérifie le rôle de l'utilisateur en faisant une requête sur... profiles :
CREATE POLICY "Users can read own profile"
ON profiles FOR SELECT
USING (
auth.uid() = id
OR (
SELECT role FROM profiles -- ← BOUCLE ICI
WHERE id = auth.uid()
) = 'admin'
);Supabase évalue cette politique → lit profiles → évalue la politique → lit profiles... à l'infini.
La solution : utiliser auth.jwt() ou une fonction SECURITY DEFINER
Il y a deux approches selon ton cas :
Option 1 — Lire le rôle depuis le JWT (recommandé)
Si tu stockes le rôle dans les claims JWT (ce que Supabase permet via les custom claims), tu peux y accéder directement :
CREATE POLICY "Users can read own profile" ON profiles FOR SELECT USING ( auth.uid() = id OR (auth.jwt() ->> 'role') = 'admin' );
Pour peupler ce claim, ajoute une fonction trigger qui met à jour le JWT après chaque login via supabase_auth_admin.update_user_by_email().
Option 2 — Créer une fonction SECURITY DEFINER
Une fonction SECURITY DEFINER s'exécute avec les droits du créateur (postgres), contournant le RLS :
CREATE OR REPLACE FUNCTION public.get_my_role() RETURNS text LANGUAGE sql SECURITY DEFINER STABLE AS $$ SELECT role FROM public.profiles WHERE id = auth.uid(); $$; -- Dans ta politique, utilise la fonction : CREATE POLICY "Users can read own profile" ON profiles FOR SELECT USING ( auth.uid() = id OR public.get_my_role() = 'admin' );
Vérifier que le RLS est bien configuré
Après avoir corrigé, teste dans le SQL Editor de Supabase :
-- Simuler un user standard
SET LOCAL role TO authenticated;
SET LOCAL request.jwt.claims TO '{"sub": "ton-user-id"}';
SELECT * FROM profiles LIMIT 1;Si ça retourne sans erreur, le fix est bon.
Autres causes de l'erreur
- Politique sur
public.usersqui référence une autre table avec une politique similaire - Jointures dans une politique entre deux tables qui s'appellent mutuellement
- Vues matérialisées avec RLS activé et politiques récursives
Résumé rapide
Checklist pour corriger l'erreur RLS :
- Identifier la politique qui fait une auto-jointure
- Remplacer par
auth.jwt()si le rôle est dans les claims - Sinon, créer une fonction
SECURITY DEFINER - Tester via le SQL Editor en simulant le contexte user
Si ton projet a plusieurs tables avec des politiques inter-dépendantes, le bug peut être plus difficile à tracer. C'est exactement ce genre de problème que les experts Last 20% résolvent — avec un rapport expliquant exactement ce qui se passait.