# Fix Failing GitHub Actions Checks in a FastAPI App

Yes. When your FastAPI build fails, an agent reads the failed log, reruns pytest against Postgres with your migrations, fixes the cause and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/fix-failing-ci/fastapi/), in a Project that has your repository.

## What the agent does

The agent's first step is the GitHub log. Given the `workflow_run` event, it runs `gh run view <run id> --log-failed` and finds the failing step. In a FastAPI app built on fastapi/full-stack-fastapi-template, `.github/workflows/test-backend.yml` is the backend job: it sets up Python from `.python-version`, installs uv, runs `docker compose up -d --wait db mailpit`, then in `backend/` runs `uv run bash scripts/prestart.sh` (Alembic migrations and `app/initial_data.py`), `uv run bash scripts/tests-start.sh`, and finally `uv run coverage report --fail-under=90`. A red job can mean a failing test, a migration that does not apply, or coverage that dropped under 90 percent, and each needs a different fix, so the agent quotes the step and its error first.

It rebuilds that job in its sandbox. `.python-version` says 3.14 and `backend/pyproject.toml` requires `>=3.14,<4.0`, with pytest, coverage, mypy, ty and ruff in the `dev` group, all pinned through `uv.lock`; the agent installs with `uv sync` so it gets the same versions CI resolved. The tests need Postgres and Mailpit (`compose.yml` runs `postgres:18`), so it starts them, runs `alembic upgrade head` and `python app/initial_data.py` the way `prestart.sh` does, and only then runs pytest, because a test that fails on a missing table is a migration problem, not a code one.

It reproduces the failure narrowly before fixing it. The suite lives in `backend/tests/`, with `api/routes/test_items.py`, `test_users.py`, `test_login.py` and `test_private.py`, plus `crud/test_user.py`; `tests/conftest.py` provides a session-scoped `db` fixture, a module-scoped `TestClient`, and `superuser_token_headers` and `normal_user_token_headers`. The agent runs the failing test by node id, such as `pytest tests/api/routes/test_items.py::test_read_item_not_enough_permissions`, reads the traceback into `app/api/routes/items.py`, `app/crud.py` or `app/models.py`, and fixes the code the failing commit changed. When the failure is an Alembic error, it fixes or adds the revision under `app/alembic/versions` instead.

If coverage was the failing step, the fix is a test for the new code path, never a lower threshold. The agent then reruns the full sequence the job runs, migrations, `tests-start.sh` and the coverage report, so the pull request passes every step and not only the one that failed.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "The GitHub Actions workflow on this branch failed; the workflow_run event is at the end of this prompt. Read the failed job's log with gh run view <run id> --log-failed and quote the failing step and error. Recreate the job in your sandbox from the workflow file: the Python version it reads, uv sync from the lockfile, the database and mail services it starts, and the migration script it runs before the tests. Reproduce the failure with pytest, running the failing test by node id first. Fix the cause in the code or the Alembic revision (change a test only if the test is wrong, and say so; never lower the coverage threshold), rerun every step of the job until it passes, and open a pull request against the failing branch that quotes the error it fixed.\n\nEvent: <workflow_run payload>",
  "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

A pull request against the branch that failed, holding the fix in the route, CRUD helper, model or Alembic revision the traceback pointed to, and a new test when coverage was the failing step. The description quotes the error from the GitHub log, names the step, and lists the commands the agent reran in its sandbox: migrations, the single test, the full suite and the coverage report. The backend workflow runs again on the pull request, so its checks confirm the fix on GitHub, and the trajectory keeps every command with its output.

When the failure is outside the code, such as `docker compose up --wait` timing out on the runner or a new action version, the agent says so and opens no pull request. Its answer arrives on your webhook with the run, naming the step and what it saw.

A reviewer checks that the fix matches the failing test's intent, that permission checks in the routes (the `403` "Not enough permissions" branches) are unchanged unless the bug was there, and that any new Alembic revision has a working `downgrade` and sits on the right head.

## Run it on every failed backend workflow

Point a GitHub webhook for `workflow_run` at your server and filter on the workflow name, here `Test Backend`, with `conclusion: failure`. Send this request with the event in `input` and `https://github.com/<owner>/<repo>/tree/<head_branch>` in `resources`, so the agent works on the branch that failed. Leave out the agent's own fix branches so a failed fix does not start another run.

Rush POSTs `run.completed` or `run.failed` to the webhook URL on your API key when the turn ends, with the answer and the session URL; your server can comment the pull request link on the failing pull request. Keep each `workflow_run.id` beside the returned `execution_id` so GitHub's redeliveries are ignored.

## Why run it on Rush?

This template's tests need Postgres, Mailpit, migrations and seed data before a single test runs, so reproducing a CI failure locally means rebuilding the whole stack on the right Python. The agent does that in its own sandbox with its own clone and its own services, leaving your Docker volumes, your local database and your branch untouched, and it starts as soon as the run fails.

The pull request carries its own evidence: the quoted log, a trajectory with every migration, pytest and coverage command and its output, and the same workflow rerunning on the pull request. Several red branches each get a separate run and sandbox, and each fix waits for a person to review it.

## Frequently asked questions

### Will it lower the coverage threshold to make CI pass?

No. The prompt rules that out, and when `--fail-under=90` is the failing step the fix is a test for the uncovered code. Any change to the workflow file shows in the diff.

### Does it need Docker to start Postgres?

It starts the database the way your workflow does. The template uses `docker compose up -d --wait db mailpit`; if the sandbox cannot run that, it starts Postgres directly with the same database name and credentials from `.env`, and says which it used in the pull request.

### What if the failing job is the frontend or Playwright workflow?

This request names pytest, so it is written for the backend job. The template also has `playwright.yml` and `pre-commit.yml`; filter your webhook by workflow name and send a prompt for that job's command instead.

### Does it run mypy and ruff too?

When the failing step is a lint or type check, yes, with the versions in `uv.lock`. Otherwise it runs them before pushing only if your workflow does, so the pull request is judged by the same checks as any other.
