# Review Pull Requests Automatically in a Fastify API

Yes. An agent checks out the Fastify pull request, starts MySQL, runs the node:test suite and posts one review that cites file and line.

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

## What the agent does

The agent reads the repository before the diff, because a Fastify app is mostly convention. In an API shaped like fastify/demo, `package.json` pins `fastify ^5.10.0` with `type: module`, `typescript ~6.0.3` and `@types/node ^26.1.1`, and the `test` script is `npm run db:seed && c8 npm run test:run`, where `test:run` is `glob -c "tsx --test" "./test/**/*.ts"`. There is no Jest and no Vitest; the suite is Node's own `node:test` with `assert` from `node:assert`. `src/app.ts` registers three `@fastify/autoload` passes in order, `plugins/external`, then `plugins/app`, then `routes` with `autoHooks` and `cascadeHooks` on, and sets the error handler that hides any 5xx message behind `Internal Server Error`. Knowing that order is how the agent tells whether a new plugin in the diff is loaded before the code that decorates with it.

Then it sets up the environment the way `.github/workflows/ci.yml` does. That workflow runs on Node 22 and 24 with a `mysql:8.4` service, installs with `npm i --ignore-scripts`, type-checks with `npm run build -- --noEmit`, lints with `npm run lint` (neostandard through `eslint.config.js`), generates a `COOKIE_SECRET`, creates an empty `.env` because the scripts pass `--env-file=.env`, then runs `npm run db:migrate && npm run test` with `MYSQL_DATABASE=test_db`, `MYSQL_USER=test_user`, `CAN_SEED_DATABASE=1` and `RATE_LIMIT_MAX=4`. The agent starts the same MySQL from `docker-compose.yml`, applies `migrations/001.do.users.sql` through `005.do.user_roles.sql` with Postgrator through `scripts/migrate.ts`, seeds the `basic`, `moderator` and `admin` users from `scripts/seed-database.ts`, and runs build, lint and test on the pull request's head commit.

The review reads the diff against the parts of the app that enforce behaviour. `src/routes/api/autohooks.ts` returns `401` for any `/api` request without `request.session.user` except `/api/auth/login`, so a route added outside `src/routes/api/` has no session check and the review says so. `src/plugins/app/authorization.ts` decorates `request.isAdmin` and `request.isModerator`; `DELETE /api/tasks/:id` uses the first as a `preHandler` and `POST /api/tasks/:id/assign` the second, so a new destructive route with neither is a blocking finding. Every route in `src/routes/api/tasks/index.ts` declares a TypeBox `schema` with `body`, `params` and `response`, and `src/server.ts` runs Ajv with `removeAdditional: 'all'`, so a response field missing from the `200` schema is silently dropped; the review flags a handler that returns a column the schema does not list. A change to `src/schemas/tasks.ts` is checked against the migration that created the column.

Findings cite file and line and are split into blocking and suggestion. Blocking: a route that bypasses the session hook, a `knex` write outside the transaction pattern used in `/:id/upload`, a password path that no longer goes through `passwordManager` in `src/plugins/app/password-manager.ts` (scrypt with a 16 byte salt and `timingSafeEqual`), or a changed `test/helper.ts` that makes `build()` skip `app.ready()`. Suggestions: a new route with no `tags`, which `hideUntagged: true` in `src/plugins/external/swagger.ts` keeps out of the OpenAPI document, or a test that reaches into `app.knex` to set state instead of calling the route. The review quotes the `npm test` summary and the c8 output, and does not push a commit.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Review this pull request on a Fastify 5 API that uses @fastify/autoload, TypeBox schemas, Knex on MySQL and node:test. Check it out. Start MySQL 8.4 as docker-compose.yml describes, export the variables .github/workflows/ci.yml sets (MYSQL_HOST, MYSQL_PORT, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, COOKIE_SECRET, COOKIE_NAME, RATE_LIMIT_MAX=4, CAN_SEED_DATABASE=1), create an empty .env, then run npm i --ignore-scripts, npm run build -- --noEmit, npm run lint, npm run db:migrate and npm test. Read the diff with src/app.ts, src/routes/api/autohooks.ts, src/plugins/app/authorization.ts and the schema file each route imports. Look for routes outside src/routes/api that skip the session hook, destructive routes without isAdmin or isModerator, handlers whose return value has fields the response schema drops, Knex writes that should share a transaction, and tests that set state through app.knex instead of the route. Post one GitHub review that quotes the build, lint and test results, 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 one review on the pull request and the full command record on the session. The review opens with the three results, `npm run build -- --noEmit`, `npm run lint` and `npm test`, as they ran against MySQL in the sandbox, then lists findings under the file and line they concern. A failing test is quoted with its `node:test` name and the assertion message, not summarised. The agent posts no fix commit, so the author's branch is exactly what the checks on GitHub ran.

A reviewer checks three things before merging. That a route the diff adds lives under `src/routes/api/` or has its own session check, because `autohooks.ts` only guards that folder. That a changed response schema still lists every field the handler returns, since Ajv's `removeAdditional: 'all'` makes a missing field a silent data loss rather than an error. And that any new test uses `build()` from `test/helper.ts` and `app.inject`, so it runs against the seeded database the same way `test/routes/api/tasks/tasks.test.ts` does.

The session's transcript holds every `tool_use` and its `tool_result`: the Docker start, the migration output, the c8 table. `GET /o/{org}/sessions/{id}` returns the review text as `result` once the turn has finished. A follow-up to the same session can ask for one test file to be run again, with the sandbox and its MySQL container woken where they were parked.

## Run it on every pull request opened or updated

Add an automation to the Project for this repository with this prompt and a `github` trigger on `pull_request.opened` and `pull_request.synchronize`, with `main` in `branch_patterns`. The trigger appends the pull request to the prompt, so each pull request and each later push gets its own sandbox, its own MySQL container and its own review. Only pull requests by an owner, member or collaborator fire it; one from a fork never does.

Set `read_only` on the automation so a run can comment and cannot push. The repository takes weekly Dependabot pull requests for npm and GitHub Actions, grouped as `.github/dependabot.yml` describes, and `max_runs_per_hour` on the automation bounds a morning when several of them open at once.

## Why run it on Rush?

Reviewing a Fastify pull request properly means running it: a MySQL 8.4 container, five migrations, a seed, and a suite that exercises the rate limit at `RATE_LIMIT_MAX=4` and the session cookie. On a laptop that collides with whatever database and `.env` are already there. A Rush run does it in its own sandbox from a clean clone, with every command and its output kept, and parks the sandbox when the turn ends.

The findings are specific because the app's rules are in files the agent can read: `autohooks.ts` for sessions, `authorization.ts` for roles, TypeBox schemas for every response. A review that quotes `npm test` against a real database is evidence the pull request's own CI then repeats. Several pull requests get reviewed at once, each with its own container, and each author gets one review to answer.

## Frequently asked questions

### Does it need a real MySQL, or can it mock Knex?

It starts MySQL 8.4 from `docker-compose.yml` in the sandbox, as CI does. The suite in `test/` builds the app with `build()` from `test/helper.ts` and calls routes with `app.inject` against seeded tables, so a mocked Knex would not run the existing tests. The review says when a container could not start and what it ran instead.

### Will it flag the error handler hiding messages?

No. `src/app.ts` returns `Internal Server Error` for any status of 500 or above on purpose, and `test/app/error-handler.test.ts` asserts that. A finding is a change that breaks that contract, such as a handler that sends a stack trace in its own reply.

### What about a pull request that only touches migrations?

The agent applies the new `.do` file with `npm run db:migrate`, checks the matching `.undo` file exists, and reads any schema in `src/schemas/` that should gain or lose a column. It then runs the full suite, since `scripts/seed-database.ts` truncates every table and inserts users and roles.

### Does the review cover the OpenAPI document?

As a suggestion. `src/plugins/external/swagger.ts` sets `hideUntagged: true`, so a route without `tags` in its schema is missing from `/api/docs` without any test failing. The review names such routes and leaves the choice to the author.

### Can it also run the suite on Node 22 and 24 like CI?

It runs on the sandbox's Node and states the version from `node --version` in the review. The two-version matrix in `ci.yml` runs on GitHub when the pull request's checks start, and the review does not report those as done.
