All jobs

Turn GitHub Issues into Pull Requests for a Fastify API

Yes. Label a GitHub issue on a Fastify API and an agent reproduces it with a node:test case against MySQL, fixes the route and opens a pull request that closes it.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads the issue, then reads how the app is wired before it edits a route. In an API like fastify/demo, src/app.ts loads src/plugins/external/ first (env, knex, session, rate-limit, swagger, under-pressure and the rest), then src/plugins/app/ (the repositories, password-manager.ts, authorization.ts, file-manager.ts), then src/routes/ with autoHooks: true. Routes are files, so src/routes/api/tasks/index.ts is /api/tasks and src/routes/home.ts is /. src/routes/api/autohooks.ts demands request.session.user on everything under /api except /api/auth/login. An issue that says a request returns 401 unexpectedly is answered by that one file, not by the route it names.

Each route declares a TypeBox schema, and the schema is where many reported bugs live. src/schemas/tasks.ts defines TaskSchema with assigned_user_id optional, CreateTaskSchema with name and assigned_user_id, and QueryTaskPaginationSchema with page at least 1, limit between 1 and 100 with a default of 10, and order defaulting to desc. src/server.ts runs Ajv with coerceTypes: 'array' and removeAdditional: 'all', so a field the issue says is ignored is usually one the schema does not list. src/schemas/users.ts requires a password of at least 8 characters with an upper case letter, a lower case letter, a digit and a symbol, and src/schemas/common.ts caps strings at 255 characters, which is also the VARCHAR(255) width in migrations/001.do.users.sql and 002.do.tasks.sql.

The reproduction is a node:test case in the file that already covers the route. test/helper.ts exports build(), which registers the app through fastify-plugin so decorators are visible, plus app.login(email) and app.injectWithLogin(email, opts), which post to /api/auth/login and attach the session cookie named by COOKIE_NAME. The seeded users from scripts/seed-database.ts are basic@example.com, moderator@example.com and admin@example.com, all with Password123$, and the roles nest so that admin holds all three. A test for a moderator-only path such as POST /api/tasks/:id/assign logs in as moderator; a test for DELETE /api/tasks/:id logs in as admin, because src/plugins/app/authorization.ts sends 403 otherwise. The new test fails on the issue's input first, and the agent quotes that failure in the pull request.

The fix is small and stays in the layer the issue names: the route handler, the repository in src/plugins/app/tasks/tasks-repository.ts (where paginate orders by created_at then id to break ties, and update returns null when no row changed), or the schema. A column change gets a numbered pair of .do and .undo files in migrations/, because scripts/migrate.ts runs Postgrator over that directory. Then the agent runs the five steps .github/workflows/ci.yml runs, npm i --ignore-scripts, npm run build -- --noEmit, npm run lint, npm run db:migrate and npm test with MySQL 8.4 from docker-compose.yml and RATE_LIMIT_MAX=4 and CAN_SEED_DATABASE=1 exported, and opens a pull request whose body says Closes #<issue>.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Fix the GitHub issue that follows this prompt in this Fastify 5 API. Read src/app.ts for the plugin load order and src/routes/api/autohooks.ts for the session check before touching a route. Reproduce the reported behavior with a node:test case in the existing test file for that route, using build(), app.inject and app.injectWithLogin from test/helper.ts and the seeded basic, moderator and admin users. Run it alone with npx tsx --test <file> and confirm it fails. Fix the cause in the route, the repository or the TypeBox schema; add a numbered .do and .undo migration if a column changes. Start MySQL 8.4 from docker-compose.yml, export the variables .github/workflows/ci.yml sets including RATE_LIMIT_MAX=4 and CAN_SEED_DATABASE=1, create an empty .env, and run npm i --ignore-scripts, npm run build -- --noEmit, npm run lint, npm run db:migrate and npm test until all pass. Open a pull request whose description says Closes #<issue>, explains the cause and quotes the failing then passing test.\n\nIssue #57: Listing tasks with ?limit=100&page=2 returns tasks from page 1 again when two tasks were created in the same second.",
  "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

What comes back is a pull request with Closes #<issue> in its body. The diff holds the new test case in the existing file under test/routes/api/, the change in src/, and when a column moved, a new numbered pair in migrations/. The description names the cause in the words of the code, such as a response schema that dropped a field or a where clause in the repository, and quotes the test failing before the fix and the npm test summary after it.

The reviewer reads the test first and checks it would fail on the main branch. Then they check the fix stayed in scope: no change to autohooks.ts to make a request pass, no widened password pattern in src/schemas/users.ts, no .do file without its .undo. The pull request's own CI workflow runs the suite on Node 22 and 24 against a fresh MySQL, so the sandbox's result is confirmed on GitHub before merge.

The session keeps the whole record: the tsx --test run that failed, the edits, the migration output and the final c8 table. GET /o/{org}/sessions/{id} returns the pull request on the session once the turn has finished, and a follow-up can ask the same sandbox to add a second case to the test.

Run it on every GitHub issue labelled agent

In the console, add an automation to the Project with this prompt and a github trigger on issues.labeled, filtered to the agent label in config.labels. The trigger appends the issue after the prompt, so you leave the example issue out of the automation's prompt. Only labels added by an owner, member or collaborator fire it, by GitHub's author_association, so a stranger's issue cannot start a run. Each labelled issue gets its own sandbox, branch and pull request.

The same request can come from your own issue tracker or triage bot through POST /o/{org}/sessions, with the issue's text after the prompt and the delivery id as the Idempotency-Key. Set a webhook_url on the key and Rush POSTs run.completed when the turn finishes, with the session_url that reads the pull request.

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

Why run it on Rush?

A reproduction in this codebase needs MySQL 8.4, five migrations, a seed with three users and roles, a session cookie and the exact environment the suite expects, including CAN_SEED_DATABASE=1 or npm test stops before the first case. That is an afternoon on a laptop that has a different database running. A Rush run builds it from the repository's own files in its own sandbox and keeps every command, so the pull request shows the failing test rather than asserting there was one.

The fix is proportionate because the agent reads the files that define behaviour, autohooks.ts, authorization.ts, the schemas and the repository, before changing a line. Several labelled issues run at once, each on its own branch, and each ends in a pull request whose own CI confirms what the sandbox saw, or in a note that explains why the issue could not be reproduced.

Frequently asked questions

Does the test need a real MySQL?

Yes. The suite runs app.inject against seeded tables through Knex, and test/helper.ts builds the whole app. The agent starts mysql:8.4 from docker-compose.yml in the sandbox as CI's service container does, applies the migrations with npm run db:migrate and seeds with npm run db:seed through npm test.

The issue is about a 401. Will it edit the auth hook?

It reads src/routes/api/autohooks.ts first, which returns 401 for every /api route without a session except the login route. If the issue's route is meant to be public, the fix is to place it outside src/routes/api/, as src/routes/home.ts is, not to loosen the hook.

What if the fix needs a new column?

The agent adds a numbered .do and .undo pair in migrations/, as 003.do.user_tasks.sql and 003.undo.user_tasks.sql are paired, updates the TypeBox schema in src/schemas/ so the response lists the column, and runs npm run db:migrate before the suite.

Which user does the test log in as?

The one the route requires. DELETE /api/tasks/:id has request.isAdmin as a preHandler and POST /api/tasks/:id/assign has request.isModerator, both from src/plugins/app/authorization.ts. The seed gives admin every role and moderator two, so the test uses the least privileged user that passes.

Will it touch the OpenAPI document?

Only if the fix adds or changes a route's schema. src/plugins/external/swagger.ts builds the document from route schemas with hideUntagged: true, so a new route gets tags so it appears at /api/docs, and the pull request says so.