# Turn Linear Issues into Pull Requests for a Next.js App

Label the issue and an agent reads it, implements it in your Next.js app's server actions and pages, checks the build and opens a pull request that links the issue.

[Run this on Rush](https://cloud.getrush.ai/run/linear-issue-to-pr/nextjs/), in a Project that has your repository.

## What the agent does

The run starts with the issue and the code side by side. A `resources` entry of the form `https://linear.app/<workspace>/issue/<ID>` puts the issue into the prompt through your Linear connection, and the repository entry clones the app. For an issue like "let a team owner rename the team from the settings page" in an app shaped like nextjs/saas-starter, the agent reads `package.json` (Next.js 15 canary with `next dev --turbopack`, React 19.1, Drizzle ORM with `drizzle-kit`, `postgres`, `jose`, `bcryptjs`, `zod`, `swr`, Tailwind 4, and no `test` or `lint` script), `next.config.ts` (`experimental.ppr` and `clientSegmentCache`), `middleware.ts` (redirects `/dashboard` without a `session` cookie and re-signs the cookie on GET) and `.env.example` (`POSTGRES_URL`, `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `BASE_URL`, `AUTH_SECRET`).

Then it finds where the feature belongs. `lib/db/schema.ts` already has `teams.name` as a non-null `varchar(100)`, `team_members.role`, and an `ActivityType` enum used by `logActivity`. The settings area is `app/(dashboard)/dashboard/`: `layout.tsx` lists the Team, General, Activity and Security pages, `page.tsx` is a client component that reads `/api/team` and `/api/user` with SWR and drives `removeTeamMember` and `inviteTeamMember` through `useActionState`, and `general/page.tsx` shows the pattern for an editable form bound to `updateAccount`. The actions live in `app/(login)/actions.ts`, each a zod schema passed to `validatedActionWithUser` from `lib/auth/middleware.ts`, and the queries in `lib/db/queries.ts` (`getUserWithTeam`, `getTeamForUser`). Nothing has to be invented; the agent follows the shape that is there.

The change is small and in the project's idiom: a `teamNameSchema` (`name` from 1 to 100 characters, matching the column) and an `updateTeamName` action that loads the caller with `getUserWithTeam`, refuses when the membership role is not `owner`, updates `teams.name` and `updatedAt`, and logs a new `ActivityType.UPDATE_TEAM` entry; the enum gains the value and `dashboard/activity/page.tsx` gains an icon for it in `iconMap`. On the Team page a card with the current name, an `Input` and a `Button` from `components/ui/` posts to the action with `useActionState`, shows the returned error or success, and calls SWR's `mutate('/api/team')` so the header updates. Members who are not owners see the name without the form.

The repository has no test runner, so the agent checks the change the way the project can be checked: it starts a Postgres in the sandbox, writes the five variables from `.env.example` into `.env`, runs `pnpm db:migrate`, then `pnpm build` and `npx tsc --noEmit`, and opens the page in its browser as a signed-up owner to rename the team and see the activity entry. If the issue asks for tests, it adds Vitest with one file for the new action and a `test` script rather than a whole harness. The pull request description links the issue, explains the role check and the enum change, and quotes the build output.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Do the work the Linear issue given as a resource describes: let a team owner rename the team from the settings page. Read the issue and its comments first. Follow this app's patterns: a zod schema and a validatedActionWithUser server action in app/(login)/actions.ts, a Drizzle update in lib/db, an ActivityType entry logged through logActivity, and a card on the Team page with the existing Input and Button components and useActionState. Only members whose role is owner may rename. Run pnpm db:migrate against a local Postgres, then pnpm build and tsc --noEmit, and exercise the page in the browser. If the project has no test runner, add Vitest with one test for the new action rather than a full harness. Open a pull request whose description links the issue and quotes the build result. Do not merge.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-org/issue/ENG-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 pull request on a new branch whose diff touches `app/(login)/actions.ts` (the schema and the action), `lib/db/schema.ts` (one enum value), `app/(dashboard)/dashboard/page.tsx` (the rename card), `app/(dashboard)/dashboard/activity/page.tsx` (the icon), and, when a test runner was added, `package.json`, `vitest.config.ts` and one test file. No migration is needed because `teams.name` already exists, and the description says so.

The description links the issue by its URL, summarises the role rule in one sentence, and quotes the `pnpm build` and `tsc --noEmit` results; the session's record holds the browser steps, so a reviewer can see the rename happen and the activity row appear without checking out the branch.

Before merging, a reviewer checks that the owner check reads the membership role from `team_members` rather than trusting the client, that the zod limit matches the `varchar(100)` column, that `mutate('/api/team')` is called so the header shows the new name, and that the activity enum change did not leave `iconMap` missing a key, which would fail type-checking.

## Run it on every Linear issue labeled agent

Add an automation to the Project with this prompt and a `linear` trigger on `linear.issue.labeled`, with `config.labels` naming the label your team uses to hand work to an agent. Each labeled issue starts one run with the issue as its context, so a ticket with a clear acceptance line turns into a pull request without anyone copying it into a prompt. Changes made by an app integration never fire the trigger, so a bot that labels issues starts no runs.

Pair it with `linear.issue.state_changed` and `config.states` to make a two-step flow: a state mapped to `plan` starts a read-only run that comments its plan on the issue, and the next state mapped to `exec` starts the run that opens the pull request. Set `max_runs_per_hour` on the automation so a bulk relabel does not start a run per issue at once.

## Why run it on Rush?

A Next.js app with server actions, Drizzle and a Postgres dependency is quick to change and slow to set up for one small ticket: a database, five environment variables, a migration, and a build before you can see the page. On Rush the agent does that in its own sandbox with a browser, and the pull request arrives with the build output and a record of the page working, so the ticket's cost is the review, not the setup.

Issues arrive in batches. Each labeled issue gets its own run and its own sandbox, so ten tickets on a Monday become ten pull requests to review rather than ten branches on one laptop, and the trail from issue to pull request to build result is kept for each.

## Frequently asked questions

### How does the agent see the Linear issue?

As a `resources` entry with the issue's URL, read through your Linear connection; the issue's title, description and comments are in the prompt before the first file is opened. A repository that no GitHub App installation covers, or an issue with no Linear connection, is refused when the run is created rather than failing later.

### The app has no tests. What counts as proof?

The build (`pnpm build`), the type check (`tsc --noEmit`) and the browser session in the record where the rename is performed and the activity log shows it. If the issue asks for tests, the agent adds Vitest with one test for the action and a `test` script, and says so in the description.

### Will it add a database migration?

Only when the schema changes. For a team rename the `teams.name` column already exists, so `drizzle-kit generate` has nothing to emit and the agent leaves `lib/db/migrations/` alone. An issue that needs a new column gets a generated migration in the same pull request.

### Does it need Stripe to work?

Not for this issue. `STRIPE_SECRET_KEY` is set to a placeholder so the module loads; the pricing page and checkout are not exercised. An issue that touches billing would need a Stripe test key stored as a Project secret.

### Can it comment the pull request link on the issue?

Not yet from inside the run. A Linear sign-in connects, but a run cannot name it as a connection, so the agent links the issue from the pull request and your handler for `run.completed` can post the link back to Linear.
