# Review Supabase Migrations in Every Pull Request

Yes. On every pull request an agent reads new Supabase migrations against your live schema and flags missing row-level security, broad grants and slow locks.

[Run this on Rush](https://cloud.getrush.ai/run/review-database-migrations/supabase/), in a Project that has your repository.

## What the agent does

The agent checks out the pull request's branch in its own sandbox and lists the new files under `supabase/migrations/`. In a Next.js and Supabase app like Razikus/supabase-nextjs-template, the repository holds `nextjs/`, `supabase/` and an Expo app, and `supabase/migrations/` has four files: `20250107210416_MFA.sql`, `20250130165844_example_storage.sql`, `20250130181110_storage_policies.sql` and `20250130181641_todo_list.sql`, with older ones kept apart in `supabase/migrations_for_old/`. `supabase/config.toml` sets `major_version = 15`, so the agent reads each statement against Postgres 15 behavior.

Before it judges the new SQL, it reads the project the migration will land on. Through the Supabase connection, Supabase's own MCP server at `mcp.supabase.com`, it queries the linked project's current tables, `pg_policies`, the `relrowsecurity` flag on each table in `public`, existing indexes and table sizes. That is what turns a guess into a finding: a table the migration alters may already have policies that the new column bypasses, and a lock on a near-empty table is not the same as one on a table that holds most of the data. It does not run the migration against the project; the prompt forbids it, and it reads only.

Then it reviews each statement in the context the template already sets. `todo_list.sql` is the pattern to compare against: it enables row level security, adds a policy `Owner can do everything` for `authenticated` using `authenticative.is_user_authenticated() AND (owner = auth.uid())`, and the MFA migration defines that function as `SECURITY DEFINER` in its own `authenticative` schema so policies require `aal2` once a user has a verified factor. A new table without `enable row level security`, or with a policy that skips `is_user_authenticated()` and so ignores MFA, is the first comment. So is a policy `to public` or `using (true)` on user data.

It also reads what the generated SQL grants. The template's own migration grants `delete`, `insert`, `select`, `update`, `truncate`, `references` and `trigger` on `todo_list` to `anon` and `authenticated`, the shape `supabase db diff` writes. Row level security does not apply to `TRUNCATE` or `REFERENCES`, so the agent comments where a new table repeats those grants. It flags a foreign key with no index (`todo_list.owner` references `auth.users(id)` with only the primary key indexed), a `SECURITY DEFINER` function with no fixed `search_path`, `auth.uid()` called per row instead of `(select auth.uid())`, and any `ALTER TABLE` that rewrites a table the live project shows is large.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Review this pull request's changes under supabase/migrations. Use the Supabase connection to read the current schema, row level security flags, policies, indexes and table sizes of the linked project; do not run the migration and do not write to the project. Post one GitHub review that flags tables without row level security, policies that grant more than the app needs or skip the project's MFA check, grants such as truncate to anon, migrations that take long locks on large tables, SECURITY DEFINER functions without a fixed search_path, and foreign keys without an index, each with file and line. Do not push commits.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-supabase-app/pull/123"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is one review on the pull request with comments on the lines of the new `.sql` files. Each comment names the table or function, says what is wrong (no row level security, a policy `to public`, a missing index on `owner`, a grant row level security does not cover), and gives the SQL to fix it: the `enable row level security` line, a policy that matches the project's existing ones, a `create index concurrently`, a `revoke truncate ... from anon`.

The review's summary lists what it read from the live project to reach each finding, such as the policies already on a table the migration alters and the row count behind a lock warning. The session's transcript keeps each query the agent sent through the Supabase connection and its answer, so a reviewer can see that the project was only read.

Before merging, a reviewer checks that every new table in `public` has row level security and a policy, that each policy uses the same helper the others use, and that any grant to `anon` is one the app really needs. A table with no policy at all is the comment to resolve first.

## Run it on every pull request opened

An automation with a GitHub trigger on `pull_request.opened` and `pull_request.synchronize` starts this review on each pull request and each push to it. To skip pull requests with no schema change, have your server receive GitHub's `pull_request` webhook, check whether any changed file is under `supabase/migrations/`, and only then send this body with the pull request URL in `resources`.

Connect Supabase once with `POST /me/connections` and its sign-in, then save an agent that names that connection, or name it in the run's `connections`. A key run gets only the connections it names, so a review started without it says it could not read the live schema rather than guessing.

## Why run it on Rush?

A Supabase migration is only safe or unsafe relative to the project it lands on: the policies already there, the helper functions the other tables use, and how big each table is. Reviewing that by hand means opening the dashboard or psql for every pull request. On Rush the agent reads the live project through the Supabase connection inside its own sandbox, with the repository checked out beside it, and compares the two in one place.

The review cites the evidence for each comment, from the line in the migration to the policy query that shows what is already there. Reviews for several pull requests run at once, each in its own sandbox, and the run's record shows every query sent to Supabase, which matters when the reviewer is a model with access to a production project.

## Frequently asked questions

### Will the agent change my Supabase project?

The prompt tells it to read only and never to run the migration, and the session's transcript records every call it made through the connection, so you can check. If you want a hard limit, connect a Supabase account or project whose access is read-only.

### How does the run reach Supabase?

Connect Supabase once with `POST /me/connections`, which returns a sign-in link you open while signed in to the console. Then name the connection in the run's `connections` field, or save an agent that names it and send the agent's id as `agent`.

### Does it understand our existing policy helpers?

Yes, it reads them from the repository and the live project. In the template that is `authenticative.is_user_authenticated()`, which demands `aal2` from users with a verified MFA factor, and a new policy that checks only `auth.uid()` gets a comment that it skips that requirement.

### Can it also test the policies?

This prompt only reviews. Ask it in a follow-up to add pgTAP tests under `supabase/tests/` and run `supabase db test` against a local stack in its sandbox, and it opens a pull request with them.
