All jobs

Run a Large Refactor on an Elixir Codebase

Yes. An agent moves repeated Ecto queries into query modules in small commits, runs mix test after each, and opens one PR listing every commit.

Run this on RushIn a Project that has your repository.

What the agent does

The agent maps the refactor before it moves anything. In a Phoenix app like hexpm/hexpm, every context module starts with use Hexpm.Context, which imports Ecto.Query (from/1 and from/2), Ecto.Changeset and the audit helpers, so queries are written inline wherever they are needed. It lists every from( under lib/hexpm/, groups them by schema, and marks the ones that repeat: schema modules such as lib/hexpm/accounts/key.ex already return queries from all/1, get/2 and get_revoked/2, each starting from assoc(user_or_organization, :keys) and filtering on query_revoked(k), while lib/hexpm/accounts/keys.ex and others build similar ones again. lib/hexpm/repository/package.ex alone has 41 from( calls. The one existing query module, Hexpm.Repository.Package.SearchQuery, sets the naming the new ones follow.

Then it plans the commits so each one is small and green on its own: one schema at a time, a new <Schema>.Query module with composable functions that take and return an Ecto.Query, callers switched over, the old inline copies deleted in the same commit. It does not change what any query returns. Where two near-identical queries differ in a where or a preload, it keeps both behaviours as separate functions or as an option, and says which in the commit message, rather than merging them and changing results.

After each commit it runs the checks the repository's own CI runs: mix compile --warnings-as-errors, mix format --check-formatted and mix test. The test alias in mix.exs runs ecto.create --quiet, ecto.load --skip-if-loaded and ecto.migrate before the suite, so the sandbox has a PostgreSQL server for it, as .github/workflows/main.yml does with its postgres:18 service on OTP 29.1 and Elixir 1.20.4. With about 300 test files under test/, a regression in a moved query shows up at the commit that moved it, not at the end.

If a step cannot keep the suite green (a query whose behaviour depends on a subtle join, a test that relies on ordering the refactor would change), the agent stops at that commit, leaves the branch at the last green state, and writes a summary of what it tried and what blocked it. Otherwise it pushes the branch and opens one pull request that lists each commit with the schema it covered.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Refactor this Elixir codebase: move the context modules' repeated Ecto queries into query modules, one module per schema, named like the existing Hexpm.Repository.Package.SearchQuery. Each query function takes and returns an Ecto.Query so callers can compose them. Do not change what any query returns. Work in small commits, one schema per commit, deleting the inline copies in the same commit. After each commit run mix compile --warnings-as-errors, mix format --check-formatted and mix test against a local PostgreSQL. If a step cannot keep the tests green, stop at the last green commit and write a summary of what blocked it. Open a pull request listing each commit with the schema it covered and any query you deliberately left inline.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-phoenix-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

What comes back is one pull request on a new branch made of many small commits. In a codebase shaped like hexpm, each commit adds a module such as lib/hexpm/accounts/key/query.ex or lib/hexpm/repository/release/query.ex, rewrites the callers in the context modules (keys.ex, releases.ex, packages.ex) to pipe through it, and deletes the inline from( blocks it replaced. The description lists each commit, the schema it covered, and the queries left inline on purpose, such as one-off admin queries that gain nothing from a shared function.

Because each commit was tested on its own, a reviewer can review the pull request commit by commit, and git bisect stays useful later. The session's trajectory keeps the mix compile, mix format and mix test output for every commit, so the claim that the suite stayed green at each step is checkable. The repository's CI then runs the same jobs on the pull request.

If the agent stopped early, the pull request (or its answer, when nothing was worth pushing) says which commit is the last green one and what blocked the next. Before merging, a reviewer checks that no query changed its result set, that preloads still happen where callers expect them, and that the new module names read well in the call sites.

Run it on every monthly cleanup slot

A refactor like this is rarely a one-off. Keep a list of mechanical cleanups (move queries for one context, rename a module tree, replace a deprecated call) and have a scheduled job send one request per item on the first Monday of the month, each scoped to one context so the pull requests stay reviewable. Each request is its own run in its own sandbox, so two cleanups never share a branch.

Set a webhook URL on the API key and Rush POSTs run.completed or run.failed when each run's turn finishes, with the final answer up to 16 KiB and the session_url. Your job can post the pull request link to the team channel, or the summary when the agent stopped at a red step.

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

Why run it on Rush?

A refactor across dozens of context modules is long, careful work that mostly consists of waiting for mix test with a database, one commit at a time. On a laptop that means the machine stays awake, Postgres stays running and the branch stays checked out until it is done. On Rush the agent installs Erlang, Elixir and PostgreSQL in its own sandbox, with its own clone and its own build cache, started with one request, and the run does not depend on your laptop being open.

What you review is evidence, not a promise. Every commit has its compile, format and test output in the trajectory, the pull request lists what moved, and CI runs on the result. Because runs are independent, you can split a refactor by context and run several at once, each in its own sandbox with its own pull request.

Frequently asked questions

Does the sandbox have PostgreSQL for mix test?

The agent installs and starts PostgreSQL in its sandbox before running the suite, because the test alias creates, loads and migrates the database first. It matches the version your CI uses where it can, such as the postgres:18 service in hexpm's workflow, and says in the pull request if it had to use another.

What if a moved query breaks a test?

It fixes the move, not the test, unless the test was checking something the refactor was meant to change, which this one is not. If it cannot get back to green, it stops at the last green commit and writes a summary, so the branch is never left half-moved.

Can I follow along or change direction halfway?

Yes. GET /o/{org}/sessions/{id}/events streams the run live, and once a turn finishes you can send a follow-up with POST /o/{org}/sessions/{id}/messages, for example "skip the billing context". A parked sandbox costs nothing and wakes where it left off, with its clone and database state intact.

Will it touch the web layer under hexpm_web?

Only where a controller or LiveView builds an Ecto query inline, which the refactor covers. The request limits the change to moving queries without changing results, so templates, routes and plugs stay as they are.