All jobs

Set Up GitHub Actions CI for a Next.js App

Yes. An agent reads your Next.js repo, adds a GitHub Actions workflow with pnpm caching, lint and type checks, and fixes it until it passes.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts by reading what the repository already declares instead of assuming a template. In a Next.js app like nextjs/saas-starter it opens package.json and finds next pinned to 15.6.0-canary.59, typescript 5.8, drizzle-kit and postgres, and a scripts block with dev, build, start and the db:* scripts but no lint and no test. It finds pnpm-lock.yaml at lockfileVersion: '9.0' with no packageManager field, no .nvmrc and no engines, so it picks the pnpm major from the lockfile and the Node major from @types/node (^22), and writes both into the workflow rather than leaving them to the runner's defaults.

Next it works out what the build and the database need. lib/db/drizzle.ts throws when POSTGRES_URL is unset, lib/payments/stripe.ts reads STRIPE_SECRET_KEY at import, and lib/auth/session.ts reads AUTH_SECRET. pnpm db:setup is interactive (it prompts through readline and calls the Stripe CLI), so the agent leaves it out of CI. It adds a postgres service container with a health check, sets POSTGRES_URL to that container, gives AUTH_SECRET, BASE_URL and a test-mode Stripe placeholder as job environment, and runs pnpm db:migrate so drizzle-kit applies lib/db/migrations/0000_soft_the_anarchist.sql to a fresh database on every run.

Where the repository has no lint or test command, the agent says so instead of inventing one silently. For lint it adds ESLint with eslint-config-next, a flat eslint.config.mjs and a lint script, because there is no config for next lint to use. Type checks run as pnpm exec tsc --noEmit, which the existing tsconfig.json already supports with strict and noEmit. With no test files in the repository, the database job is the migration plus pnpm build; when your repository does have a test script, the agent runs that against the service container instead.

Before pushing, it runs the same commands in its sandbox: pnpm install --frozen-lockfile, the lint script and tsc --noEmit, so the first push is not a guess. Then it opens the pull request, reads the workflow run that pull request triggers, and if a step fails it reads that step's log, fixes the cause in the branch and pushes again, until the new workflow is green on its own pull request.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Add a GitHub Actions workflow at .github/workflows/ci.yml that runs on every pull request and on pushes to main. Read package.json and pnpm-lock.yaml first: use the pnpm version the lockfile implies and pin the Node version, set up pnpm with actions/setup-node caching keyed on pnpm-lock.yaml, and cache .next/cache. Run lint, a TypeScript check with tsc --noEmit, and the tests against a postgres service container with POSTGRES_URL pointing at it, applying the Drizzle migrations with pnpm db:migrate first. If the repository has no lint or test script, add the smallest one that works and say so in the pull request description. Do not touch existing workflows. Open a pull request and make sure the new workflow passes on it; if a step fails, fix it and push again.",
  "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 on a new branch. In a repository shaped like nextjs/saas-starter it adds .github/workflows/ci.yml, plus eslint.config.mjs and the lint script and dev dependencies in package.json and pnpm-lock.yaml when the repository had no linter. The workflow has one job with a postgres service, pnpm/action-setup with the pinned pnpm version, actions/setup-node with cache: pnpm, an actions/cache step for .next/cache, and then install, lint, type check, migrate and build or test as separate named steps, so a failure points at one step.

The pull request's own checks are the proof: the CI workflow it adds runs on it, and the description says which commands each step runs, which scripts were added because they did not exist, and which environment values are placeholders. The session's trajectory keeps every command the agent ran in its sandbox with its output, along with any pushes it made after reading a failed step.

Before merging, a reviewer checks that the Node and pnpm versions match what the team uses locally, that the placeholder secrets are not real keys, that the new lint rules are ones the team wants to adopt, and that the workflow's triggers and branch filters fit how the repository is protected. Once merged, the workflow can be made a required check in the branch protection settings.

Run it on every new repository

Add a GitHub organization webhook for the repository event and, on created, have your server send this same request with the new repository in resources. Each repository gets its own run in its own sandbox and its own pull request, so a template repository that ships without CI is covered the day someone creates a project from it. The Rush GitHub App has to be installed on all repositories, or on the new one, for resources to reach it.

Set a webhook URL on the API key and Rush POSTs run.completed or run.failed when each run's turn finishes, with the session URL, so your server can post the pull request link to the team channel. A key runs at most max_concurrent sessions at once (4 by default) and answers 429 past that, so a backfill across many existing repositories should queue its requests.

The use case this job follows, with its automation and trigger: Hand off from your terminal.

Why run it on Rush?

Writing a CI workflow on a laptop means pushing, waiting for the runner, reading a red step and pushing again, often several times, while the branch sits in your working copy. On Rush the agent does that loop in its own sandbox with its own clone: it runs pnpm install --frozen-lockfile and tsc --noEmit there first, then reads the run on GitHub and pushes fixes itself. Your machine and your working tree are not involved, and the run keeps going if you close the laptop.

The result is checkable rather than claimed. The workflow that proves the change is the change, running on the pull request, and the trajectory keeps every command and its output, so a reviewer can see why db:setup was left out or why a lint config was added. Because each run is independent, the same request can add CI to a dozen repositories at once, each with a pull request a person reviews before anything merges.

Frequently asked questions

Will it change my existing workflow files?

The prompt tells it not to, and it adds a new .github/workflows/ci.yml. If a workflow with that name already exists, it says so in the pull request instead of overwriting it. Everything it changes is in the pull request diff, so nothing reaches your default branch until you merge.

Does it work with pnpm, npm, Yarn or Bun?

It uses the package manager your lockfile shows: pnpm-lock.yaml, package-lock.json, yarn.lock or bun.lock. For pnpm without a packageManager field, it pins the version in pnpm/action-setup so the runner does not pick a different major than the lockfile was written with.

My app has no tests. What does the test step run?

It says that in the pull request rather than adding a placeholder test that always passes. The database job then applies your Drizzle or Prisma migrations to the Postgres service container and runs next build, which catches broken migrations and type errors in server code. Ask for a first test in the prompt and it adds one with the test runner you name.

What about my Stripe and auth secrets?

The workflow uses placeholder values for build-time variables like STRIPE_SECRET_KEY and AUTH_SECRET, and the pull request lists each one. Real keys never go into the workflow file; if a step genuinely needs one, the agent names the GitHub Actions secret to create and references it as ${{ secrets.NAME }}.

Does it wait for the workflow to pass before it finishes?

Yes, that is part of the request: it reads the workflow run on the pull request, and when a step fails it reads the log, fixes the cause and pushes again. If something outside the repository blocks it, such as Actions being disabled for the organization, it stops and says so in its answer instead of reporting success.