# Review Pull Requests on a dbt Project

Yes. An agent checks out the pull request, runs dbt build on DuckDB in its sandbox, and posts one review on grain, join fan-out and missing tests.

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

## What the agent does

The agent reads the project before the diff. In dbt-labs/jaffle-shop, `dbt_project.yml` names the project `jaffle_shop`, requires `dbt >=2.0.0`, points at a profile called `default` that no file in the repository defines, keeps data tests in `data-tests/`, builds `staging` models as views and `marts` as tables, and enables the CSV seeds under `seeds/jaffle-data/` only when the variable `load_source_data` is true. `packages.yml` asks for `dbt-labs/dbt_utils` 1.4.1 and `dbt-audit-helper` from git at `main`, and `package-lock.yml` freezes that revision. Two macros matter for running anywhere: `cents_to_dollars` dispatches to `default__`, `postgres__`, `bigquery__` and `fabric__` versions, and `generate_schema_name` sends seeds to a `raw` schema.

The repository's own CI cannot run on a fork. `.github/workflows/ci.yml` starts dbt Cloud jobs on Snowflake, BigQuery and Postgres with a `DBT_CLOUD_API_KEY` secret, so a pull request from outside gets no build. The agent builds instead on DuckDB in its sandbox: it installs `dbt-core` and `dbt-duckdb`, writes a `profiles.yml` for the `default` profile with a local database file, runs `dbt deps`, `dbt seed --full-refresh --vars '{"load_source_data": true}'` and then `dbt build` on the pull request branch. The project's SQL is portable enough for that: `stg_orders` uses `dbt.date_trunc`, `stg_supplies` uses `dbt_utils.generate_surrogate_key`, and `metricflow_time_spine` uses `dbt.date_spine`, all of which dispatch for DuckDB. `.sqlfluff` names the `dbt-cloud` templater and the Snowflake dialect, so the agent lints with the `dbt` templater against its own profile and leaves that file unchanged.

Then it reads the diff against the grain the YAML declares. `models/marts/orders.yml` says one row per order and tests `order_id` with `unique` and `not_null`, `customer_id` with a `relationships` test to `stg_customers`, and two `dbt_utils.expression_is_true` checks, `order_items_subtotal = subtotal` and `order_total = subtotal + tax_paid`; it also carries a unit test, `test_order_items_compute_to_bools_correctly`, a semantic model and a saved query. `orders.sql` keeps its grain by aggregating `order_items` to one row per `order_id` before the left join, and `order_items.sql` does the same with supplies, summing `supply_cost` per `product_id` before joining. A change that joins a one-to-many table without that summary step, or adds a column to `orders` without a line in `orders.yml`, is what the review is for.

For each candidate finding the agent runs a query in DuckDB rather than arguing from the SQL: `count(*)` against `count(distinct order_id)` on the built `orders` table for grain, row counts before and after a new join for fan-out, and `dbt test --select <model>` for whether an existing test already pins the behavior. A new metric or semantic model is checked for a matching entity and `agg_time_dimension`. One GitHub review is posted per run with the `dbt build` summary quoted, blocking issues separated from suggestions, and every finding citing file and line. The prompt forbids pushing commits, so the agent pushes nothing.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Review this dbt pull request. Check it out, install dbt-core and dbt-duckdb, write a profiles.yml for the project's `default` profile on a local DuckDB file, run dbt deps, seed the sample data with load_source_data true, and run dbt build. Read the diff against the grain each model's YAML declares. For every candidate finding run a query: compare count(*) with count(distinct <key>) for grain, compare row counts before and after any new join for fan-out, and check whether a column added to a model has a test and description in its YAML. Post one GitHub review that quotes the dbt build result, separates blocking issues from suggestions, and cites file and line for every finding. Do not push commits.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/42"
    }
  ]
}
```

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 a review on the pull request. Its summary quotes the `dbt build` result from DuckDB, the number of models, tests and unit tests that ran and how they ended, then lists the findings with file and line in `models/marts/` or `models/staging/`, marked as blocking or suggestion. Inline comments sit on the cited lines, and a grain or fan-out finding includes the query the agent ran and the two counts it compared.

The session's trajectory keeps the `profiles.yml` it wrote, the seed and build output, every query behind a finding, and the YAML files it opened while checking tests. When the review says a new column has no test, the trajectory shows the model's YAML as it read it.

Before merging, a reviewer runs the quoted queries on their own warehouse, since DuckDB proves the logic and not the warehouse-specific behavior, and dismisses suggestions that do not block. A blocking fan-out finding is closed by adding the aggregation step the other marts use, not by adding `distinct`.

## Run it on every pull request opened

Create an automation in the Project with a `github` trigger on `pull_request.opened` and `pull_request.synchronize`, limited to the branches you merge to, and this prompt. The trigger adds the pull request after the prompt, so every pull request gets its own sandbox, its own DuckDB build and its own review, with no change to the dbt Cloud jobs that run for maintainers.

The review is the result: it appears on the pull request, and the run shows in the Project like one started from the console. The automation's max runs per hour, 1 to 30, caps a burst of small pull requests; a fire past it is skipped as `rate_limited` and that pull request waits for its next push, so set the cap to the busiest hour you expect.

## Why run it on Rush?

Reviewing a dbt change from the diff alone misses what only the data shows: a join that doubles rows, a mart whose declared grain no longer holds, a metric over a column nobody tested. Running the project needs a warehouse and credentials most reviewers do not have on hand, and the repository's CI runs only for people with the dbt Cloud key. On Rush the agent builds the whole project on DuckDB in its own sandbox and keeps every command and query, so the review rests on an executed build.

One sandbox per pull request keeps reviews parallel when several model changes land in a week, each with its own seeded database. The author keeps their branch, the reviewer gets findings with the queries behind them, and nothing touches the production warehouse.

## Frequently asked questions

### The project targets Snowflake, BigQuery and Postgres. Is a DuckDB build meaningful?

For grain, fan-out and test coverage, yes: those are properties of the joins and the YAML, not the warehouse. The macros in this project dispatch by adapter, so the build runs. Behavior that depends on a warehouse-specific function is named in the review as something to confirm there.

### Does it need the dbt Cloud API key?

No. It never calls dbt Cloud. The CI jobs in `.github/workflows/ci.yml` stay as they are for maintainers, and the agent's build is separate and local to its sandbox.

### What about the semantic layer YAML?

The agent parses it with the same `dbt build`, which fails on a malformed semantic model or metric. It checks that a new metric has an entity and a time dimension on its model, but it cannot query metrics without a Semantic Layer endpoint and says so if that is what the pull request changes.

### Will it run the unit tests?

Yes. `dbt build` runs unit tests such as `test_order_items_compute_to_bools_correctly` in `orders.yml` before the model builds. A pull request that changes a model's logic without updating its unit test fails there, and the review quotes the failing case.

### Can it lint with the project's SQLFluff config?

Mostly. `.sqlfluff` uses the `dbt-cloud` templater, which needs a dbt Cloud session, so the agent runs `sqlfluff lint` with the `dbt` templater pointed at its own profile and the same rules. Lint findings are posted as suggestions, not blockers, unless the prompt says otherwise.
