# Turn Linear Issues Into Pull Requests for a React App

Yes. Label a Linear issue and an agent implements it in your React app's components, machines or Express routes, adds the test and opens a linked pull request.

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

## What the agent does

The agent reads the Linear issue first, then maps it onto the app's structure before writing code. In an app like cypress-io/cypress-realworld-app, a screen is three files: a container in `src/containers/` (`TransactionCreateContainer.tsx`, `UserSettingsContainer.tsx`, `BankAccountsContainer.tsx`), the components it renders from `src/components/`, and the XState machine in `src/machines/` that owns the data and the requests (`createTransactionMachine.ts`, `bankAccountsMachine.ts`, `userOnboardingMachine.ts`). The API is `backend/app.ts`, which mounts `/users`, `/contacts`, `/bankAccounts`, `/transactions`, `/likes`, `/comments`, `/notifications` and `/bankTransfers`, each a router file in `backend/` that calls functions in `backend/database.ts` over a lowdb JSON file. An issue about a form goes into a component and its machine; an issue about what the API returns goes into the route and `database.ts`, and often both.

It follows the app's own conventions rather than inventing a layer. Forms use Formik with a Yup schema, as `SignUpForm.tsx` and `BankAccountForm.tsx` do, and the elements tests click carry `data-test` attributes that `cypress/support/commands.ts` reads through `cy.getBySel` and `cy.getBySelLike`, so a new control gets one. Request bodies are validated with express-validator chains in `backend/validators.ts` and passed through `validateMiddleware` from `backend/helpers.ts`, which answers `422` with the error array. Money is stored as integer cents: `createTransaction` in `backend/database.ts` multiplies the submitted amount by 100 and the UI formats with `dinero.js`. A new field crosses all of that: the model in `src/models/`, the validator, the route, the machine's context and the component.

The test goes in the layer that can see the change. A pure function in `src/utils/` or a `database.ts` function gets a Vitest case in `src/utils/__tests__/` or `src/__tests__/`, where tests call `seedDatabase()` in `beforeEach` and run with `fileParallelism: false` because they share `data/database.json`. A component gets a Cypress component spec beside it, named `*.cy.tsx`, like `SignInForm.cy.tsx`. A user flow gets a case in the matching file under `cypress/tests/ui/`, which starts with `cy.task('db:seed')`, intercepts the requests it waits on with aliases, and signs in with `cy.loginByXstate`. An API change gets a case in `cypress/tests/api/`. The agent picks the smallest layer that proves the issue's acceptance criteria.

It runs what `.github/workflows/main.yml`'s `install` job runs, with Node 22.20.0 from `.node-version` and `yarn install --frozen-lockfile`: `yarn types`, `yarn lint` (eslint and `prettier --check` at 100 columns), `yarn test:unit:ci` and `yarn build:ci`. For a Cypress case it starts the built stack with `yarn start:ci`, waits on ports 3000 and 3001, and runs that one spec with `yarn cypress:run --spec`. Then it opens a pull request whose description links the Linear issue, lists the files by layer and quotes each result, and says which specs it did not run. The run cannot comment on the Linear issue itself; your handler posts the pull request link back.

## Start it with one API call

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

```json
{
  "agent": "codex",
  "input": "Implement the Linear issue given as a resource in this React 18 app with an Express API. Read the issue and its comments first. Map the change to its layer: the container in src/containers, the component in src/components, the XState machine in src/machines, the route in backend/ and the function in backend/database.ts, and change only the layers the issue needs. Follow the existing patterns: Formik with a Yup schema for forms, a data-test attribute on every new control, an express-validator chain in backend/validators.ts for every new body field, and integer cents for amounts. Add the smallest test that proves the acceptance criteria: a Vitest case in src/__tests__ or src/utils/__tests__, a Cypress component spec beside the component, or a case in the matching file under cypress/tests/ui or cypress/tests/api. Use Node 22.20.0 and yarn install --frozen-lockfile, then run yarn types, yarn lint, yarn test:unit:ci and yarn build:ci; for a Cypress case start yarn start:ci and run that spec alone. Open a pull request whose description links the issue, lists the files by layer, quotes each result and names any spec you did not run. 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 its own branch with the Linear issue linked in its description. The diff is grouped the way the app is: a model in `src/models/`, a validator and route in `backend/`, a machine in `src/machines/`, a component and its `data-test` attribute, and one test in the layer that proves the behaviour. The description quotes `yarn types`, `yarn lint`, `yarn test:unit:ci` and `yarn build:ci`, and for a flow change the single `cypress run` summary with its browser.

A reviewer checks the layers agree with each other: that the validator in `backend/validators.ts` accepts exactly what the form sends, that the machine's context carries the new field to the component, and that an amount is multiplied by 100 once and only once. They check the test would fail without the change and that no spec gained a numeric `cy.wait`. The push runs `main.yml` on GitHub, so the five-container Chrome and Firefox matrix confirms what the sandbox ran.

The session holds the whole record, including the issue text the run read and every command. `GET /o/{org}/sessions/{id}` returns the pull request once the turn has finished; your handler reads it there or from the `run.completed` webhook and comments the link on the issue.

## Run it on every Linear issue labelled agent

Create an automation in the Project for this repository with this prompt and a `linear` trigger on `linear.issue.labeled`, with the hand-off label in `config.labels` and your team keys in `config.teams`. Each labelled issue starts one run with the issue as its context, on its own branch; a label added by an app integration never fires it. Leave the example issue URL out of the automation's prompt, since the trigger supplies the issue.

For a two-step flow, add a `linear.issue.state_changed` trigger whose `config.states` maps a review state to `plan` and a later state to `exec`. The `plan` run reads the containers, machines and routes the issue touches, changes nothing and posts its plan as a comment on the issue; the `exec` run opens the pull request once someone has read it.

## Why run it on Rush?

A small React issue in this app crosses five files in three layers, and proving it means Node 22.20.0, a frozen yarn install, a type check, a build and a browser running one Cypress spec against two servers. A Rush run does that in its own sandbox with a browser, from a clean clone, and keeps the output of every command, so the pull request can quote the spec result instead of promising one.

The change is proportionate because the agent reads the issue against the app's own structure, containers, machines, validators and cents, before editing. Several labelled issues run at once, each on its own branch with its own build, and each ends as a linked pull request whose own CI matrix confirms it, or as a comment on the issue saying what was unclear.

## Frequently asked questions

### How does the run read the Linear issue?

Through the `https://linear.app/<workspace>/issue/<ID>` entry in `resources`, which puts the issue in the prompt through your Linear connection, or through the `linear` trigger, which appends the issue after the prompt. An issue your Linear connection cannot reach answers `422 RESOURCE_NOT_ACCESSIBLE` before a sandbox starts.

### Which test layer will it choose?

The smallest that proves the acceptance criteria. A `database.ts` change gets a Vitest case in `src/__tests__/`, a component change a `*.cy.tsx` spec beside it, a flow change a case in `cypress/tests/ui/`. The pull request says which and why, and names the specs it left to GitHub.

### Will it add a new API endpoint for a UI change?

Only when the issue needs data the existing routes in `backend/` do not return. The agent prefers extending a route and its validator chain over adding a parallel one, and a GraphQL change in `backend/graphql/` is made only when the issue names that path.

### Does the run comment the pull request link on the Linear issue?

No. The run reads the issue, and the pull request links it, but posting back is your handler's job: read the pull request from the session or the `run.completed` webhook and comment it through your own Linear integration.

### What if the issue needs one of the third-party sign-in flows?

`src/index.auth0.tsx`, `index.okta.tsx`, `index.cognito.tsx` and `index.google.tsx` select a provider by `VITE_*` variables, and their specs under `cypress/tests/ui-auth-providers/` need credentials the sandbox does not have. The agent implements the change, runs the local-session path, and lists the provider spec as not run.
