What the agent does
The run starts from a workflow_run.failure event, which names the workflow, the head branch and the commit. The agent clones that commit and reads the failed job's log with gh run view <run id> --log-failed. In an API like fastify/demo there is one workflow that matters, .github/workflows/ci.yml, with a single build-and-test job on a matrix of Node 22 and 24 and a mysql:8.4 service container. Its steps are in a fixed order: npm i --ignore-scripts, npm run build -- --noEmit, npm run lint, a generated COOKIE_SECRET, touch .env, then npm run db:migrate && npm run test. The agent finds which step printed the first error, because a type error in npm run build and an assertion in npm test need different work. lock-threads.yml runs on a monthly cron and is never the cause.
It rebuilds the job's environment rather than guessing. MySQL 8.4 starts from docker-compose.yml, and the agent exports what the workflow's Test step sets: MYSQL_HOST=localhost, MYSQL_PORT=3306, MYSQL_DATABASE=test_db, MYSQL_USER=test_user, MYSQL_PASSWORD=test_password, COOKIE_NAME=sessid, RATE_LIMIT_MAX=4 and CAN_SEED_DATABASE=1. Two of those are easy to miss and each breaks the suite on its own. src/plugins/external/env.ts lists COOKIE_SECRET, COOKIE_NAME and COOKIE_SECURED as required, so a missing secret fails at app.ready() in every test. And scripts/seed-database.ts throws unless CAN_SEED_DATABASE is 1, which makes npm test fail before a single test runs, since the test script is npm run db:seed && c8 npm run test:run.
With the environment in place the agent reproduces the failure with the job's own command, then narrows it. test:run is glob -c "tsx --test" "./test/**/*.ts", so one file runs as npx tsx --test test/routes/api/tasks/tasks.test.ts. The suite depends on the seeded basic, moderator and admin users and on RATE_LIMIT_MAX=4: test/app/rate-limit.test.ts sends four requests and expects the fifth to be 429, and the first case in test/routes/api/users/users.test.ts expects PUT /api/users/update-password to refuse a fourth attempt inside a minute, from the per-route rateLimit of max: 3 in src/routes/api/users/index.ts. A failure in those files after a change to src/plugins/external/rate-limit.ts is a changed limit, not a flaky test. A failure in tasks.test.ts after a change to src/schemas/tasks.ts is usually a response schema that no longer lists a returned column, because Ajv in src/server.ts runs with removeAdditional: 'all'.
The fix goes where the cause is: src/, a migration under migrations/, or the test when the test is wrong, and the pull request says which. For a lint failure the agent runs npm run lint:fix and keeps only the lines the failing commit touched; eslint.config.js extends neostandard with a comma-dangle: never rule, so a trailing comma is the usual culprit. For a type error it runs npm run build -- --noEmit and reads the file and line from tsc. It reruns build, lint, migrate and the full npm test before opening the pull request, and it never raises RATE_LIMIT_MAX, skips a test or adds --ignore-scripts to anything beyond the install step the workflow already uses.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "The GitHub Actions workflow on this branch failed; the workflow_run event follows this prompt. Read the failed job's log with gh run view <run id> --log-failed and quote the first failing step and error. Rebuild the job's environment from .github/workflows/ci.yml: start MySQL 8.4 as docker-compose.yml describes, export MYSQL_HOST, MYSQL_PORT, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, a generated COOKIE_SECRET, COOKIE_NAME, RATE_LIMIT_MAX=4 and CAN_SEED_DATABASE=1, and create an empty .env. Run npm i --ignore-scripts, npm run build -- --noEmit, npm run lint, npm run db:migrate and npm test, running the one failing test file alone with npx tsx --test first. Fix the cause in src, migrations or the test if the test is wrong, and say which. Rerun all five steps until they pass. Do not raise RATE_LIMIT_MAX, skip a test, or weaken a TypeBox schema to make a test pass. Open a pull request against the failing branch that quotes the error it fixed and the passing output.\n\nEvent: <workflow_run payload>",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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.
- 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.
- 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 against the branch that failed. Its description names the workflow and the first failing step, quotes the error from the log, and shows the same command failing and then passing in the sandbox, with the Node version from node --version. The diff is the fix. A change to package.json, eslint.config.js or ci.yml is listed apart from the code because it changes every future build.
The repository's own CI workflow runs on the pull request, so the reviewer sees the Node 22 and Node 24 rows go green on GitHub rather than taking the sandbox's word for it. They check that no test lost an assertion, that RATE_LIMIT_MAX and the per-route limit in src/routes/api/users/index.ts are what they were, and that a TypeBox response schema was widened only when the column really belongs in the reply.
When the cause is outside the code, such as a registry outage during npm i or a MySQL image pull that fails, the run says so with the log lines it read and opens no pull request, instead of changing something unrelated to turn the check green.
Run it on every failed CI run on a branch
In the console, add an automation to the Project for this repository with this prompt and a github trigger on workflow_run.failure. The trigger appends the event after the prompt, so the run knows the workflow, branch and commit. Only runs on branches by an owner, member or collaborator fire it, and a failure on a fork never does. The automation's max_runs_per_hour keeps a bad dependency bump that reddens several branches from starting more runs than you want.
If your own server routes CI failures instead, it can send this body to POST /o/{org}/sessions with the failing branch as https://github.com/<owner>/<repo>/tree/<head_branch> in resources, the GitHub delivery id as the Idempotency-Key, and the event text after the prompt. The key's webhook_url then receives run.completed with a session_url that reads the pull request.
The use case this job follows, with its automation and trigger: Fix a bug from an alert.
Why run it on Rush?
A red Fastify build is rarely reproduced on the laptop that caused it, because the laptop has a .env, a local MySQL with yesterday's rows and a Node version that is not the one in the matrix. The sandbox starts from the workflow file: the same mysql:8.4 image, the same variables, migrations from zero, and the same tsx --test runner. Every command and its output stay on the session, so the pull request can quote the failure and the pass rather than describe them.
The suite is sensitive in ways that need the environment to be exact, such as RATE_LIMIT_MAX=4 and CAN_SEED_DATABASE=1, so proving a fix means running it there. When several branches fail at once, each gets its own run and its own container, and each ends with a pull request whose checks GitHub confirms, or a note saying what could not be reproduced.
Frequently asked questions
Which Node version does the sandbox use?
The workflow runs Node 22 and 24, and the sandbox runs one of them and names it. The pull request's own CI run covers the other row. If the log shows the failure on only one version, the agent installs that one to reproduce it.
The failure is in the seed step, not a test. Can it fix that?
Yes. npm test runs db:seed first, and scripts/seed-database.ts refuses to run unless CAN_SEED_DATABASE=1 and truncates every table before inserting users and roles. A seed failure is usually a migration the seed does not expect, and the fix is in migrations/ or the seed script.
Will it change the rate limit to make the 429 tests pass?
No. test/app/rate-limit.test.ts and the first case in users.test.ts encode RATE_LIMIT_MAX=4 and the max: 3 on the password route. The prompt forbids raising either, so a test that fails there means the limiter changed and the pull request fixes the limiter.
What if the lint failure is in a file the commit did not touch?
The agent fixes it, since npm run lint runs on the whole tree and the branch cannot go green otherwise, but it lists that change separately in the pull request and keeps it to what lint:fix produced.