# Set Up GitHub Actions CI for a React Router App

Yes. An agent reads your React Router 7 app's scripts, Node version and lockfile, adds a workflow that typechecks and builds it, and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-github-actions-ci/react-router/), in a Project that has your repository.

## What the agent does

The agent clones the app into its own cloud sandbox and reads what there is to run before it writes a workflow. In a React Router 7 app made from remix-run/react-router-templates, `package.json` has four scripts and no test: `dev` (`react-router dev`), `build` (`react-router build`), `start` (`react-router-serve ./build/server/index.js`) and `typecheck`, which is `react-router typegen && tsc`. The order in that last script matters: `tsconfig.json` includes `.react-router/types/**/*` and sets `rootDirs` to `.` and `./.react-router/types`, so `tsc` on its own fails on a clean checkout until `react-router typegen` has written the route types. The workflow calls the script, not `tsc` directly.

Then it settles the Node version and the package manager from files rather than defaults. The template's `Dockerfile` builds every stage from `node:24-alpine` and runs `npm ci` against `package-lock.json`; in the templates repository itself `.nvmrc` says 24 while the root workflow pins Node 22 with pnpm 10 for its own Playwright suite under `.tests`. For a single app the agent uses the lockfile it finds: `npm ci` with `package-lock.json`, `pnpm install --frozen-lockfile` with `pnpm-lock.yaml`, and `actions/setup-node` with the version from `.nvmrc` or, failing that, the Dockerfile's base image, with the matching `cache` setting. An app made with `create-react-router` pins `react-router` and `@react-router/*`; the templates repository leaves them as `*` for its workspace, and the agent notes which it is looking at.

The workflow it writes to `.github/workflows/ci.yml` runs on `push` to the default branch and on `pull_request`, with `permissions: contents: read` and a `concurrency` group so a second push cancels the first. Its job installs, runs `typecheck`, then `build`, and uploads nothing: `build/client` and `build/server` are reproducible from the commit. Because the template ships a multi-stage `Dockerfile`, the agent adds a second job that runs `docker build` with `push: false`, which catches the case where `.dockerignore` or the `production-dependencies-env` stage drifts from `package.json`. With no test script in the app, it adds no test step and says so in the pull request rather than inventing a Vitest setup the repository does not have.

Before opening the pull request it runs the same steps in the sandbox on a clean clone: the install, `npm run typecheck` and `npm run build`, and `docker build` when Docker is available, so the first CI run on GitHub is not the first time the commands are tried. It also checks `vite.config.ts`, which loads `@tailwindcss/vite` and `reactRouter()`, and `react-router.config.ts`, where `ssr: true`, since an app switched to SPA mode builds different output and the workflow's comments should say what to expect.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Set up GitHub Actions CI for this React Router 7 app. Read package.json for the scripts (typecheck is react-router typegen && tsc, so call the script, not tsc), the lockfile to pick npm, pnpm or yarn, .nvmrc and the Dockerfile for the Node version, react-router.config.ts for ssr, and vite.config.ts. Write .github/workflows/ci.yml that runs on push to the default branch and on pull_request, with contents: read permissions and a concurrency group, installs from the lockfile with the matching setup-node cache, runs the typecheck script and then the build script. If a Dockerfile exists add a job that builds the image without pushing. Add no test step unless package.json has a test script. Run every step in the sandbox on a clean clone first, then open a pull request that explains each choice and says what the app has no tests for.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

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 pull request with one new file, `.github/workflows/ci.yml`, and a description that names the Node version it chose and where it read it, the package manager and lockfile, the two scripts the job runs and in what order, and whether a Docker job was added. The push opens the workflow's first run on GitHub, so the pull request shows its own checks before anyone reviews it.

A reviewer reads the workflow against `package.json` once: the install command matches the lockfile, `typecheck` comes before `build`, and `setup-node` names the same major as the `Dockerfile`. If the app has since gained a `test` script, the reviewer asks for the step in a follow-up on the same session rather than editing the file by hand.

The session keeps the sandbox run of every step with its output, so a `typecheck` failure on the first CI run can be compared with what passed in the sandbox, which usually points at a difference in Node version or a file ignored by `.dockerignore`.

## Run it on every new React Router repository

When your platform team creates a repository from the template, the script that creates it also POSTs this body to `https://api.prix.dev/o/{org}/sessions` with `resources` set to the new repository and a `request_id` made from the repository name, so a retried script starts no second run. The Rush GitHub App has to be installed on the new repository first, or the resource is refused as `RESOURCE_NOT_ACCESSIBLE`.

Give the key a `webhook_url` and Rush POSTs `run.completed` when the turn finishes, with `data.session_url` from which a handler reads the pull request and posts its link in the team's channel. Every run the key starts reports to that URL; a key may have at most `max_concurrent` runs in progress, so a batch of new repositories is started a few at a time.

## Why run it on Rush?

The usual way to add CI is to push a workflow and read the failure log four times. Here the agent runs the install, the typecheck and the build on a clean clone in its own sandbox first, so the pull request's first run on GitHub is a confirmation rather than an experiment, and the transcript shows each command's output beside the file that was written.

The choices that go wrong most often, the Node major, the lockfile command and the `typegen` before `tsc`, are read from the repository rather than copied from a template, and the pull request says where each came from. Several repositories can be set up at once, each in its own sandbox, with a result that is a reviewable file and a green check rather than a promise.

## Frequently asked questions

### The app has no tests. Will the agent add a test framework?

No. The prompt says to add a test step only when `package.json` has a `test` script, and the templates ship none. The pull request says so plainly. Adding Vitest or Playwright is a separate job with its own page, and a follow-up on the same session can start it.

### Does it work with pnpm or yarn?

Yes. The agent picks the install command from the lockfile it finds, `pnpm-lock.yaml`, `yarn.lock` or `package-lock.json`, sets the matching `cache` on `setup-node`, and adds `pnpm/action-setup` when pnpm is in use, as the templates repository's own workflow does. A repository with two lockfiles is reported rather than guessed.

### Why does typecheck run before build?

Because `react-router typegen` writes the route types that `tsconfig.json` includes, and a `tsc` failure is faster and clearer than a build failure. The `build` script also runs `react-router build` through Vite, which does not typecheck, so without the first step a type error would merge.

### Will it change the Dockerfile or add deployment?

No. The Docker job only proves the image builds from the commit, with `push: false`. Deployment targets, registry logins and secrets are not in the prompt, and the agent writes nothing that needs a secret the repository does not have.
