All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.

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.