All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Review every pull request.

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.