All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Fix a bug from an alert.

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.