All jobs

Run a Large Refactor on a Rails Codebase

Hand Rush a Rails refactor that touches many files, and get a pull request in small commits, each with the test suite run after it.

Run this on RushIn a Project that has your repository.

What the agent does

In a Rails app like rubygems/rubygems.org, the prompt names one refactor with a wide blast radius: extract the gem push flow into a service object with its own tests. The flow today lives in app/models/pusher.rb, where class Pusher defines save at line 72, reached from the API controller at app/controllers/api/v1/rubygems_controller.rb. Extracting it means the controller action, the model's parsing and validation steps, and the ownership and indexing side effects each move behind a small service object with an explicit interface, while test coverage follows from test/functional/api/v1/rubygems_controller_test.rb and test/models/pusher_test.rb into tests that name the service object directly.

The agent maps the app before editing. The stack is pinned hard: .ruby-version says 4.0.6 and the Gemfile locks Rails at ~> 8.1.3 with Minitest 6, and lint.yml runs bundle exec rubocop, herb on the ERB templates, brakeman and the importmap verifier, so every commit has to stay inside those checks. The suite runs with bin/rails test against the test database, and the AGENTS.md rule matters here: regressions need a fail-then-pass test, meaning the new service object's tests must prove they catch the behavior the old flow encoded, not merely mirror its calls. Side effects like indexing and notification are teased apart so the service object owns orchestration and the model keeps persistence.

The work proceeds in small commits, each followed by bin/rails test. The agent also keeps the app's conventions: web strings live in config/locales/en.yml with bin/fill-locales run when wording moves, and backfills belong in MaintenanceTasks rather than ad hoc migrations, so anything touching existing records goes the maintenance-task route rather than into the extraction. Where a commit cannot keep the suite green, the run stops at needs_review with the failing test named; otherwise the pull request opens listing each commit and the extraction boundary in the description.

Because the push flow is also an HTTP API, the agent runs the functional test file for the controller after the boundary move to prove the endpoint's contract held. System tests exist behind Playwright and are the slowest layer, so the run reports which layers it ran rather than implying the whole matrix executed. The result is a branch where each commit passes the suite it claims to pass, and the reviewer can bisect the extraction by following the commit list.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Refactor this codebase: extract the gem push flow into a service object with its own tests. Work in small commits, run the build and tests after each, and stop at needs_review with a summary if a step cannot keep the tests green. Open a pull request listing each commit.",
  "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

The pull request arrives as a sequence of small commits, each with bin/rails test run after it. The diff extracts the push flow from app/models/pusher.rb into a service object with its own Minitest coverage, updates app/controllers/api/v1/rubygems_controller.rb to call it, and keeps the controller's functional test green as the contract check. If the run stopped early, the branch ends at the last green commit and the description names the failing test and the remaining scope.

A reviewer reads the service object's interface first: what it takes, what it returns, and which side effects it owns. The fail-then-pass test rule means its tests should fail against the old code, which is the property that makes the extraction safe to merge. The session read returns the repository and pull request information, and the transcript holds every test run, so the per-commit claim is checkable without rerunning the suite.

Run it on every refactor issue labelled

When an issue describes a mechanical extraction at application scale, your automation sends POST /o/{org}/sessions with the issue text and this prompt the moment the label lands, so the work starts while the surrounding code is still familiar. Install the Rush GitHub App on the repository first and authenticate with an API key; an Idempotency-Key header keeps a retried submission from starting two runs.

Keep the returned execution_id and read the session when the run settles, or receive the key's completion webhook. A review the next morning is the natural cadence for work this size; if a commit message undersells a hunk, send a follow-up on the finished turn and the messages endpoint wakes the parked sandbox where it left off.

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

Why run it on Rush?

Extracting a core flow like gem push means running the Minitest suite after every small commit for hours, with Postgres, memcached and OpenSearch up for the integration layers. That stack belongs in a sandbox, not on your laptop: the run proceeds on Rush's compute after you close the terminal, and the transcript preserves each test invocation so the green-per-commit claim is evidence, not assertion.

For several refactors, submit one repository per run within your key's concurrency allowance and they proceed in parallel, each with its own commit history. You review the pull requests independently, and the value you are buying is exactly the boring part: small commits, the suite run after each, and an honest stop when a step cannot stay green. The useful proof is a service object whose tests fail against the old code.

Frequently asked questions

What exactly gets extracted?

The push flow out of app/models/pusher.rb, which today reaches the API through app/controllers/api/v1/rubygems_controller.rb. The extraction gives the flow a service object with an explicit interface, moves the tests into place, and leaves the model holding persistence while the service owns orchestration. The pull request description states the boundary.

How is this kept safe on a live app's code?

Three constraints: small commits with bin/rails test after each, the app's own AGENTS.md rule that regressions need a fail-then-pass test, and the controller's functional test as the HTTP contract check. If a step cannot keep the suite green, the run stops and reports instead of pushing on.

Which checks must stay green?

The test suite, plus the lint pipeline's rubocop, herb, brakeman and importmap checks. The agent runs the layers it claims to run and reports which those were; the slow Playwright system layer is named explicitly rather than implied.

What if the extraction needs to change existing data?

The app's convention is that backfills go in a MaintenanceTasks::Task, not an ad hoc migration or a sneaky step in the extraction. The agent follows that, so data movement is reviewable as its own commit.

Can I start this from the terminal and review tomorrow?

Yes. Send the request, keep the execution_id, close the laptop. Read the session in the morning or receive the key's completion webhook; the result is either the finished pull request or a needs_review summary naming the blocker.