All jobs

Turn Linear Issues Into Pull Requests for a Phoenix App

Yes. Point a run at a Linear issue and your Phoenix repository: the agent changes the stats API, runs mix test on Postgres and ClickHouse and opens a linked PR.

Run this on RushIn 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 Phoenix application like plausible/analytics, .tool-versions pins Erlang 28.5, Elixir 1.20 and Node 24, and mix.exs defines the test alias as ecto.create --quiet, ecto.migrate, test and clean_clickhouse, because the suite needs both PostgreSQL and ClickHouse, which CONTRIBUTING.md starts with make postgres and make clickhouse. config/test.exs puts Plausible.Repo in the SQL sandbox, gives Plausible.ClickhouseRepo a pool of 15, swaps the mailer for Bamboo.TestAdapter, puts Oban in manual testing mode and replaces the HTTP client with a Mox mock; test/test_helper.exs excludes :slow, :minio and :migrations by default.

The stats API is in lib/plausible_web/router.ex: the /api/v1/stats scope, with api_scope set to stats:read:* and PlausibleWeb.Plugs.AuthorizePublicAPI in its pipeline, routes realtime/visitors, aggregate, breakdown and timeseries to PlausibleWeb.Api.ExternalStatsController, and the /api/v2 scope routes POST /query to ExternalQueryApiController.query, with its JSON schema served at /api/docs/query/schema.json. The dashboard's own endpoints go through the internal_stats_api pipeline to StatsController. A ticket about a public metric or filter lands in external_stats_controller.ex or the query layer under it; a ticket about auth lands in the plug.

Tests mirror that: test/plausible_web/controllers/api/external_stats_controller/ holds aggregate_test.exs, breakdown_test.exs, timeseries_test.exs, auth_test.exs and a dozen query_*_test.exs files, each use PlausibleWeb.ConnCase with setup [:create_user, :create_site, :create_api_key, :use_api_key] and assertions like json_response(conn, 402)["error"]. The agent writes its test there, with factories from test/support/factory.ex, and runs mix deps.get, mix compile --warnings-as-errors and mix test on the files it changed and then on the suite, as .github/workflows/elixir.yml does against postgres:18 and clickhouse-server:25.11, and opens the pull request.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Implement the Linear issue given as a resource in this Phoenix codebase. Read the issue and its comments first. Make the change in the stats API controller, the query layer under it or the authorization plug the issue concerns, and keep the diff small. Add ExUnit tests under test/plausible_web/controllers/api using PlausibleWeb.ConnCase and the existing setup helpers. Start PostgreSQL and ClickHouse in the sandbox as CONTRIBUTING.md describes, run `mix deps.get` and `mix compile --warnings-as-errors`, then `mix test` on the changed files and on the suite, and 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 links the issue, names the controller function, query module or plug it changed, and quotes the ExUnit summary. The diff holds the change and its test in the matching external_stats_controller/ file.

The pull request URL is on the session and in the run.completed webhook, and your handler posts it as a comment on the issue, since the sandbox has no Linear tools of its own. The transcript keeps the database start-up, the compile and each mix test command, so a reviewer can see that a change to aggregate was checked against aggregate_test.exs and the feature-access cases in it.

Before merging, a reviewer reads the change once for the Community Edition and once for the Enterprise one: the router wraps parts of itself in on_ee, and test_helper.exs has a ce_test mode that excludes :ee_only tests, so a change to a public endpoint needs to hold in both.

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 the 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 review state to plan, a read-only run that posts its plan on the issue, and a later state to exec, which opens the pull request. For a public API, the plan step is where a maintainer says whether a change belongs in /api/v1/stats or in the /api/v2 query endpoint.

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

Why run it on Rush?

A Plausible change cannot be tested without PostgreSQL and ClickHouse running, migrated and cleaned between runs, which is why mix test is an alias that does all of that. On Rush each ticket gets a sandbox that starts both, runs the suite and is discarded, and the output stays with the run.

The public stats API is a contract with everyone who scripts against it, so a pull request that quotes aggregate_test.exs and query_validations_test.exs passing carries weight a description cannot. Tickets run in parallel in separate sandboxes, each with its own databases.

Frequently asked questions

How does the agent read the Linear issue?

From the resources entry with its URL, read through your Linear connection and placed 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 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 need ClickHouse?

Yes. Events live in ClickHouse and the test alias ends with clean_clickhouse. The agent starts it in the sandbox, as make clickhouse does; CI uses clickhouse-server:25.11.5.8-alpine and postgres:18.

Does it run the slow tests?

Not by default: test_helper.exs excludes :slow, :minio and :migrations, and CI adds them with --include. Put mix test --include slow in the prompt when the ticket touches what they cover.