# Review Pull Requests on a React Router App

Yes. An agent checks out the pull request, runs pnpm typecheck, pnpm build and the template's Playwright test in its sandbox, and posts one review.

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

## What the agent does

The agent starts with how the repository is laid out, because in a React Router 7 project the interesting review questions depend on which mode the app runs in. In remix-run/react-router-templates, `pnpm-workspace.yaml` lists five packages: `.tests`, `default`, `node-custom-server`, `unstable_rsc-data-mode-vite` and `unstable_rsc-framework-mode`, with `allowBuilds` turned off for `esbuild`, `sharp` and `workerd` and a `minimumReleaseAge` of 10800 that exempts `react-router` and `@react-router/*`. `.nvmrc` says Node 24, while `.github/workflows/ci.yml` installs pnpm 10, Node 22 and Deno v2.3.5, then runs `pnpm playwright install --with-deps chromium` and `pnpm test --project=chromium` inside `.tests`. The agent uses the CI versions, since those are what the pull request will be judged against.

Then it reads the template the diff touches. `default/package.json` has four scripts: `build` is `react-router build`, `dev` is `react-router dev`, `start` is `react-router-serve ./build/server/index.js` and `typecheck` is `react-router typegen && tsc`; `react-router.config.ts` sets `ssr: true` and `app/routes.ts` registers one index route, `routes/home.tsx`. `app/root.tsx` exports `Layout`, `App` and an `ErrorBoundary` that uses `isRouteErrorResponse` to show 404 differently from other errors and prints a stack only when `import.meta.env.DEV` is set. `node-custom-server` adds Express 5 in `server.js`, with `compression`, `morgan` in production and Vite middleware in development, and `server/app.ts` builds a `RouterContextProvider` in `getLoadContext` so that `routes/home.tsx` can read `valueFromExpressContext` in its loader. The two RSC templates pin `@vitejs/plugin-rsc` at `~0.5.34` and `@remix-run/node-fetch-server` at `0.13.0`, and the data-mode one has three entries, `entry.browser.tsx`, `entry.rsc.tsx` and `entry.ssr.tsx`.

The checks the repository already has live in `.tests`. `playwright.config.ts` matches `**/test.*.ts`, runs one `chromium` project, and on CI uses one worker with two retries. `test.default.ts` has three tests: `typecheck` runs `pnpm typecheck`; `dev` starts `pnpm dev --port <free port>`, waits for Vite's `Local:` line, loads the page, expects the title to match `New React Router App` and the `React Router Docs` and `Join Discord` links to appear, and asserts that `page.errors` and the dev server's stderr are both empty; `build + start` runs `pnpm build`, then `pnpm start` with `PORT` set, and waits for the `[react-router-serve]` line. `test.node-custom-server.ts` does the same against `Server is running on` and filters the known `WebSocket server error` from stderr with `withoutHmrPortError`. `utils.ts` copies each template into `.tests/tmp/`, symlinks its `node_modules` and records every `pageerror`. The agent runs `pnpm install` at the root, `pnpm typecheck` and `pnpm build` in each template the diff touches, and the matching `test.<template>.ts` through Playwright, so the review quotes output rather than guessing.

With the branch built it reads the diff with the questions the prompt names. Loaders and actions: a new `loader` or `action` in `app/routes/*.tsx` should type its arguments from the generated `./+types/<route>` file, return data the component reads through `loaderData`, and in `node-custom-server` reach Express values only through `context.get(...)`. Redirects: a redirect thrown from a loader must be a `Response`, not a returned value, and a route added to `app/routes.ts` must be reachable from the index. Error boundaries: a change to `root.tsx` must keep the `isRouteErrorResponse` branch and must not print `error.stack` outside `import.meta.env.DEV`, since the `ErrorBoundary` renders in production too. The agent also checks `default/Dockerfile`, which copies `package-lock.json` and runs `npm ci` on `node:24-alpine`, so a dependency change that only updates `pnpm-lock.yaml` leaves the image build behind. It posts one review that cites file and line for every finding and pushes nothing.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Review this pull request on a React Router 7 app. Check it out, run pnpm install, then pnpm typecheck and pnpm build in every template the diff touches, and run the matching Playwright test in .tests with pnpm test --project=chromium after pnpm playwright install --with-deps chromium. Read the diff with the surrounding route modules, root.tsx, react-router.config.ts and, for node-custom-server, server.js and server/app.ts. Look for loaders and actions that are untyped or return the wrong shape, redirects returned instead of thrown, routes missing from app/routes.ts, an ErrorBoundary that leaks a stack outside DEV, and dependency changes that update pnpm-lock.yaml but not the Dockerfile's npm path. Post one GitHub review that quotes the typecheck, build and Playwright 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 a review on the pull request. Its summary quotes the `pnpm typecheck` result for each touched template, the `pnpm build` output, and the Playwright line reporter's result for `test.default.ts` or `test.node-custom-server.ts`, including any `pageerror` the test collected. Findings follow, each with file and line and marked blocking or suggestion: a loader whose return type the generated `+types` file rejects, a redirect that will render as data, a route file nobody registered.

The session's transcript keeps the install, every command the agent ran with its output, the pages it loaded through Playwright, and the files it opened while following a route from `routes.ts` into its module. When the review says the dev server printed to stderr, the transcript shows the line.

Before merging, a reviewer re-runs the Playwright test for the touched template on the branch, opens each cited line, and decides whether the suggestions are worth a follow-up. A failed `typecheck` or a non-empty `page.errors` is blocking by default; a style preference is not.

## 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`, `branch_patterns` set to `main`, and this prompt. Each fire adds the pull request after the prompt, so every pull request, and every push to it, gets its own sandbox, its own Playwright run and its own review. Pull requests from forks never fire the trigger, so a community template submission still needs a maintainer to start the run.

Mark the automation `read_only`, since a review only reads the repository and comments. The review is the result and appears on the pull request; the run shows in the Project like one started from the console. `max_runs_per_hour`, between 1 and 30, caps a burst of pushes; a fire past the cap is skipped as `rate_limited` and the next push starts a fresh run.

## Why run it on Rush?

A React Router template change looks small in the diff and is only proven by running it: the typegen step has to regenerate `+types`, the build has to produce `build/server/index.js`, and the dev and production servers have to serve the page without a console error. The `.tests` suite exists for exactly that and needs pnpm, Chromium and a free port. On Rush the agent gets its own sandbox with all three, runs the suite there and keeps the output, so a reviewer reads a result instead of trusting a green checkbox they cannot inspect.

One sandbox per pull request keeps reviews parallel when several templates change at once, and nothing is pushed to anyone's branch. The author keeps their checkout and the reviewer gets findings with the Playwright output attached, so the discussion is about the loader or the error boundary rather than about whose machine the build broke on.

## Frequently asked questions

### Which Node version does the agent use?

The CI matrix in `.github/workflows/ci.yml` runs Node 22 with pnpm 10, while `.nvmrc` says 24 and the Dockerfile uses `node:24-alpine`. The agent uses the CI version for the checks and notes in the review when a change depends on behaviour that differs between the two.

### Does it run the Playwright tests for every template?

Only for the templates the diff touches. `.tests` has a `test.<template>.ts` file per template, and the agent passes that file name to `pnpm test` so a change to `node-custom-server` does not wait for the RSC templates to build. Ask for the whole suite in the prompt if the change is shared, such as a bump to `react-router` itself.

### What about the unstable RSC templates?

They typecheck and build like the others, but `.tests` has no `test.*.ts` for them, so the review says so and relies on `pnpm typecheck` and `pnpm build` for those two. A finding in `entry.rsc.tsx` or `entry.ssr.tsx` is a reading of the code, and the review labels it as such.

### Will it accept a new template?

The README says the team intends to keep the number of templates to a minimum and is unlikely to accept new ones, pointing contributors at the community list instead. If the pull request adds a directory that is not in `pnpm-workspace.yaml`, the review points at that policy and at the missing workspace entry rather than reviewing the template line by line.
