# Fix Sentry Errors in a FastAPI App

Send the Sentry issue to an agent: it reads the FastAPI stack trace, reproduces the error with a failing pytest case, fixes the route and opens a pull request.

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

## What the agent does

The agent reads the issue after the prompt and fetches its latest event through the Sentry connection: the exception class and message, the transaction, the request data and breadcrumbs, the release and environment tags and the in-app frames. In a FastAPI app the SDK's FastAPI integration names the transaction after the route, so the frames and the transaction together point at one handler function.

In an app built from fastapi/full-stack-fastapi-template the wiring is small enough to read in full. `backend/pyproject.toml` requires Python 3.14 and pins `fastapi[standard]`, `sqlmodel`, `psycopg`, `pyjwt`, `pwdlib` and `sentry-sdk[fastapi]`, with `pytest`, `mypy`, `ruff` and `coverage` in the dev group under `uv`. `backend/app/main.py` calls `sentry_sdk.init(dsn=…, enable_tracing=True)` when `SENTRY_DSN` is set and `FASTAPI_ENV` is not `development`, then mounts `api_router` from `app/api/main.py` under `/api/v1`. That router includes `login`, `users`, `utils` and `items` from `app/api/routes/`, and `private` only in development.

The handlers in `app/api/routes/items.py` show where errors come from: `read_item`, `update_item` and `delete_item` call `session.get(Item, id)` and raise `HTTPException` 404 or 403 on purpose, so those never reach Sentry; what does is an unhandled exception, for example a database error from `session.commit()` in `create_item` or `update_item`, a `ValidationError` from a model change, or a failure in `app/api/deps.py`, where `get_current_user` decodes the JWT with `settings.SECRET_KEY`, loads the user and raises 403, 404 or 400 for the cases it anticipates. The breadcrumbs show the SQL that ran before the exception, which usually settles whether the cause is the query, the data or the request body.

Tests live in `backend/tests/`: `tests/api/routes/test_items.py`, `test_users.py` and `test_login.py`, with `tests/conftest.py` providing a session-scoped `db` that calls `init_db` and deletes `Item` and `User` rows at the end, a module-scoped `TestClient`, and `superuser_token_headers` and `normal_user_token_headers`. The agent adds one test that sends the request from the event with the right headers and confirms it fails with the same exception, fixes the handler or dependency, and runs `bash scripts/test.sh`, which is `coverage run -m pytest tests/` with `FASTAPI_ENV=development`, against the Postgres the `compose.yml` starts, the same way `test-backend.yml` runs `scripts/prestart.sh` then `scripts/tests-start.sh`. It also runs `bash scripts/lint.sh` for `mypy` and `ruff`, and opens a pull request that links the Sentry issue, quotes the frames and shows the test before and after.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Sentry issue for this FastAPI repository is attached below. Read its latest event through the Sentry connection: the exception, the transaction (the route), the request data, the breadcrumbs with the SQL that ran, the release and the in-app frames. Open the handler in backend/app/api/routes or the dependency in backend/app/api/deps.py that the frames name and read it with its SQLModel models in backend/app/models.py. Add one test to the matching file under backend/tests/api/routes that sends the same request through the TestClient and token-header fixtures from tests/conftest.py, and confirm it fails with the same exception. Fix the cause rather than catching the exception, run bash scripts/test.sh with the compose Postgres as the test-backend workflow does and bash scripts/lint.sh, and open a pull request that links the Sentry issue, quotes the frames and the failing test output and explains the cause. Stop at needs_review if you cannot reproduce it.\n\nSentry issue:\n{issue}",
  "connections": [
    "sentry"
  ],
  "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 that changes one handler in `backend/app/api/routes/` or one dependency in `deps.py`, sometimes one model in `models.py`, and adds one test case beside the existing ones, for example a case in `test_items.py` that updates an item with the body from the event and now gets 422 rather than an unhandled error. The description links the Sentry issue, names the route and the frame, states the cause, and shows the test failing then passing with the `scripts/test.sh` summary and the `lint.sh` result.

The reviewer checks that the test uses the fixtures rather than its own database setup, that the fix validates or handles the input instead of swallowing the exception, and that `mypy --strict` and `ruff` still pass, since `pyproject.toml` configures both. A change in `deps.py` deserves a look at every route, because all authenticated handlers go through `CurrentUser`.

A run that ends `needs_review` opens no pull request and says what it tried, for example that the request body in the event is valid against the current `ItemUpdate` model, so the error may already be fixed or may depend on data the test database does not have.

## Run it on every Sentry issue alert

Create an alert rule in the Sentry project with a webhook action to a handler on your server. The handler sends this request with the issue JSON after the prompt and the Sentry issue id as `Idempotency-Key`, so a repeated delivery returns the run already started rather than a second pull request. Sentry cannot add the `Authorization: Bearer` header a Rush `webhook` trigger requires, so the handler is the forwarder, to `POST /o/{org}/sessions` or to an automation's trigger in the Project.

With a webhook URL on the API key, Rush POSTs `run.completed` or `run.failed` with the `session_url`, and the handler can comment the pull request link on the Sentry issue. The handler should dedupe on issue id, since an endpoint under load can report the same issue many times a minute.

## Why run it on Rush?

The template already ships the Sentry SDK, a Postgres compose file and a pytest suite with a 90 percent coverage floor in CI, so the pieces exist; what is missing is someone to assemble them for each alert. The agent does that in its own sandbox: compose up, prestart, the one failing test, the fix, the full run. The developer reads a pull request instead of a traceback.

The evidence travels with the result. The transcript shows the event the agent read, the pytest case failing with the same exception, and the `scripts/test.sh` and `scripts/lint.sh` output after the change. Several alerts can be several sessions at once, and a run that cannot reproduce its issue stops and says so.

## Frequently asked questions

### Does the sandbox need Postgres?

Yes, and the repository provides it: `compose.yml` defines the `db` service, and `test-backend.yml` runs `docker compose up -d --wait db mailpit`, `scripts/prestart.sh` for migrations and initial data, then `scripts/tests-start.sh`. The agent follows those steps; nothing reaches a production database.

### Why does the prompt say to run with the TestClient fixtures?

Because `tests/conftest.py` already handles the session, the superuser and a normal user's token headers, and cleans up `Item` and `User` rows at the end. A test that builds its own client or database would be slower and would not match its neighbours.

### What about the frontend?

Out of scope for this page. The Sentry project for the backend reports Python exceptions from `backend/app`; a browser error from `frontend/` goes to a different project and a different kind of fix.

### Will the fix just add a try/except?

No. The prompt asks for the cause. A database integrity error should be prevented by validation or handled with the right status, a bad token should be refused in `deps.py`, and a model mismatch should be fixed in `models.py` with a migration under `backend/app/alembic` when a column changes.

### Does coverage still have to stay above 90 percent?

CI enforces `coverage report --fail-under=90`, and the pull request adds a test, so coverage normally rises. The description quotes the coverage line from `scripts/test.sh` so the reviewer sees it before CI does.
