# Turn Linear Issues Into Pull Requests for a Rails App

Yes. Give the run a Linear issue and your Rails repository: the agent changes the push or API controller, runs bin/rails test and opens a linked pull request.

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

## What the agent does

The issue goes in as a resource. `https://linear.app/<workspace>/issue/<ID>` in `resources` puts the issue in the prompt through your Linear connection, beside the repository URL that clones the code, and the agent reads description, comments and acceptance criteria before touching a file.

In a Rails application like rubygems/rubygems.org, `.ruby-version` is 4.0.6 and the `Gemfile` pins `rails` 8.1 with `clearance` for sessions, `good_job` for jobs and `flipper` for flags. The gem push is `Api::V1::RubygemsController#create` in `app/controllers/api/v1/rubygems_controller.rb`, guarded by `authenticate_with_api_key`, `verify_with_otp` and a Pundit `authorize Rubygem, :create?`, then a scope check `@api_key.can_push_rubygem?`. It accepts a raw body or a multipart upload with a `gem` file and an `attestations` array, hands both to `Pusher.new(@api_key, gem_body, request:, attestations:)` and renders `gemcutter.message` with `gemcutter.code`. `app/models/pusher.rb` does the work in `process`: `authorize`, `verify_gem_scope`, `verify_mfa_requirement`, `validate`, `save`, with `pull_spec`, `verify_sigstore`, `write_gem` and `persist_version` below.

A ticket about push behaviour lands in one of those methods; a ticket about an API response lands in the controller or its policy in `app/policies/rubygem_policy.rb`. The agent changes the method that owns the behaviour, keeps the API key scope and MFA checks as they are unless the ticket says otherwise, and writes the test in the repository's style: Minitest with `shoulda-context`, so `context "On POST to create for new gem"` blocks with `should` assertions, factories from `test/factories/` and `gem_file` from `test/helpers/gem_helpers.rb`, which reads `test-0.0.0.gem` and whose happy path expects the response `Successfully registered gem: test (0.0.0)`.

The suite needs PostgreSQL, which `.github/actions/setup-rubygems.org` provides with `docker compose up -d --wait`, a copy of `config/database.yml.sample` and `bundle exec rake db:setup assets:precompile`; `test/test_helper.rb` also loads WebMock, Mocha and Capybara. The agent reproduces that setup in the sandbox, runs the focused file and then `bin/rails test`, the `General` leg of `.github/workflows/test.yml`, plus `bundle exec rubocop` on the files it changed, and opens the pull request with the issue URL and the test output in its description.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Implement the Linear issue given as a resource in this Rails codebase. Read the issue and its comments first. Make the change in the gem push path or the API controller the issue concerns, keeping the API key scope, MFA and Pundit checks unless the issue changes them, and keep the diff small. Add Minitest tests in the repository's shoulda-context style next to the existing ones, using the factories and gem helpers. Set up the test database the way .github/actions/setup-rubygems.org does: PostgreSQL reachable as config/database.yml.sample expects, then `bundle exec rake db:setup`. Run the changed test files and `bin/rails test`, and `bundle exec rubocop` on the files you touched; fix what fails. Open a pull request whose description links the issue, explains the change and quotes the test 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

A pull request on its own branch comes back, its description linking the Linear issue, naming the controller or `Pusher` method it changed, and quoting the Minitest summary from `bin/rails test`. The diff holds the change and its tests.

The pull request URL is on the session and in the `run.completed` webhook; your handler posts it as a comment on the issue, because the sandbox has no Linear tools of its own. The transcript keeps the database setup, each test command and every file the agent opened, so a reviewer can see that a change to `verify_mfa_requirement` was read against the existing MFA tests before it was made.

Before merging, a reviewer checks the change against the acceptance criteria and against what the push path protects: API key scopes, OTP verification, ownership. The `System` leg of CI, `bin/rails test:system` with Playwright, and the `lint.yml` checks, `rubocop`, `herb analyze`, `brakeman`, `importmap:verify` and `bin/prettier`, still run on the pull request.

## Run it on every Linear issue labeled agent

Create an automation with a `linear` trigger on `linear.issue.labeled`, the hand-off label in `config.labels` and your team keys in `config.teams`. Labeling an issue starts one run with the issue as its context and this prompt; a label added by an app integration never fires it.

Add a `linear.issue.state_changed` trigger with `config.states` mapping a ready state to `plan`, so the first run only reads the code and posts a plan on the issue, and a later state to `exec` for the run that opens the pull request. For a codebase with this much around a push, the plan step is where a maintainer says which checks the ticket may touch.

## Why run it on Rush?

The rubygems.org suite wants PostgreSQL, a seeded database, precompiled assets and, for system tests, a Playwright browser. Setting that up on a laptop for a one-method ticket is the reason such tickets wait. On Rush the agent does it in a sandbox that is discarded afterwards, and every command's output stays with the run.

Changes to a gem push need proof more than most. The pull request quotes the suite and the transcript shows the `rubygems_controller_test.rb` contexts the agent ran, so a reviewer sees that the MFA and scope checks still pass rather than reading that they should. Several tickets run at once, each in its own sandbox with its own database.

## Frequently asked questions

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

From the `resources` entry with its URL. Rush reads it through your Linear connection and puts it in the prompt before the run starts. Connect Linear once with `POST /me/connections {"type": "app", "name": "linear"}`; an issue URL with no connection answers `422 RESOURCE_NOT_ACCESSIBLE`.

### Can it post the pull request link back on the issue?

Not from the sandbox; a Linear sign-in cannot yet be named in `connections`. Read the URL from the session or the webhook and post it from your handler.

### Does it run the system tests?

`bin/rails test` is the `General` leg. Add `bin/rails test:system` to the prompt when the ticket changes a view; it needs the Chromium that `bin/playwright install --with-deps chromium` fetches, which `bin/setup` runs.

### What about the Avo admin and the secrets CI uses?

CI runs a third leg, `test test/*/avo`, and sets `REQUIRE_AVO_PRO` only on the upstream repository. A fork without those secrets runs the general suite the same way, which is what the sandbox does.
