Use Case #14: RLS Policy Debugging
Debugging Row Level Security policies when queries return empty but should return data.
William Welsh
Author
Use Case #14: RLS Policy Debugging
"The data is there. I can see it in the dashboard. But the app shows nothing."
Classic RLS problem. Not classic solution.
The Symptom
User logs in. Dashboard should show their projects. Query runs. Returns empty array. But the database has 47 projects for this user.
The Investigation
Claude started systematic debugging:
Step 1: Verify the query - Ran the exact query in Supabase SQL editor with service role (bypasses RLS). Returned 47 rows. Query is fine.
Step 2: Check RLS policies - Listed all policies on the projects table. Found 3 SELECT policies. That's suspicious - usually you want 1.
Step 3: Policy Analysis - The three policies: "Users see own projects" (checks user_id), "Team members see team projects" (checks team membership), and "Admins see all" (checks admin role).
Step 4: The Bug - The team membership check called a function that queried the team_members table. That table ALSO had RLS. Which checked if you could see the team. Which queried projects. Infinite recursion.
Supabase doesn't error on RLS recursion. It just returns empty.
The Fix
Claude restructured the policies using security definer functions that bypass RLS internally, with a single entry point policy that calls the function. The function checks all conditions without triggering recursive RLS.
What I Learned
RLS debugging is hard because it fails silently. Claude's systematic approach - verify query, list policies, trace each one - found in 15 minutes what I'd been stuck on for 2 hours.
The Pattern
When RLS returns empty unexpectedly, check for policies that query other tables, RLS on those other tables, and circular dependencies between tables.
This bug appeared in two different projects. Same pattern both times.
William Welsh
Building AI-powered systems and sharing what I learn along the way. Founder at Tech Integration Labs.
Related Articles
View all →Use Case #6: Mock to Production Database
The app looked great but everything was fake. Claude created 20+ database tables, wrote RLS policies, generated seed data, and wired up real API calls.
Use Case #15: Multi-Agent Schema Discovery
The database had 200+ tables. No documentation. No ERD. Claude spawned 5 parallel agents to map relationships, infer purposes, and generate complete documentation.
Use Case #16: Deep Bug Investigation
The bug was intermittent. Sometimes it worked. Sometimes it broke. Claude spent 43 minutes tracing it through 12 files before finding a race condition in async initialization.