All jobs

Review Alembic Migrations in FastAPI Pull Requests

An agent reads the Alembic migrations a FastAPI pull request adds, applies them to a fresh sandbox Postgres with data, and reviews locks, data loss and downgrades.

Run this on RushIn a Project that has your repository.

What the agent does

The agent checks out the pull request's branch in its own sandbox and reads the migration setup before the migrations. In a FastAPI app like fastapi/full-stack-fastapi-template, backend/alembic.ini points script_location at app/alembic, backend/app/alembic/env.py takes the URL from settings.DATABASE_URL, sets target_metadata to SQLModel.metadata and configures compare_type=True, and backend/scripts/prestart.sh is the production path: alembic upgrade head followed by python app/initial_data.py. pyproject.toml requires Python >=3.14 with alembic>=1.19.1, sqlmodel>=0.0.46 and psycopg[binary], and mypy and ruff both exclude the alembic directory, so the migrations get no static check the rest of the code gets.

Then it reads the chain. The existing revisions under backend/app/alembic/versions/ go from e2412789c190 (initialize models) through 9c0a54914c78 (string lengths), d98dd8ec85a3 (integer ids replaced with UUIDs) and 1a31ce608336 (cascade delete) to fe56fa70289e (add created_at to user and item), each with a down_revision that the new file must extend. The agent runs alembic heads to confirm the pull request adds exactly one head, alembic history to see where it attaches, and reads the models in backend/app/models.py to compare what the migration does with what the User and Item tables now declare.

To review with evidence rather than opinion, the agent starts the postgres:18 the repository's compose.yml names, applies the chain up to the pull request's down_revision, loads synthetic users and items, and only then applies the new revision with alembic upgrade head while a second session holds a transaction on the affected table, measuring whether the DDL waits. It reads the SQL with alembic upgrade --sql for the offline form, looks for the patterns that hurt on a live database: a NOT NULL added without a default, a column dropped or narrowed, a type change that rewrites the table, a backfill UPDATE inside the same transaction as the DDL (d98dd8ec85a3 is the local example of that pattern, with its UPDATE "user" SET new_id = uuid_generate_v4()), and an index created without CONCURRENTLY.

Finally it runs alembic downgrade -1 and upgrade head again to see whether the downgrade restores the schema and what data it discards, runs alembic check to see whether the models and the migrated schema still agree, and runs the test suite with bash scripts/tests-start.sh, which the conftest.py session fixture drives through init_db against the same database. Each observation becomes a comment on the changed line it concerns.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Review the Alembic migrations added by the open pull request in resources. Read alembic.ini, alembic/env.py, the prestart script, pyproject.toml and the models first, and confirm the new revisions attach to the current head with alembic heads and alembic history. Start the Postgres version the repository's compose file names in the sandbox with a fresh database and synthetic data only, never a production connection. Apply the chain up to the pull request's down_revision, load representative rows, then apply the new revisions while a second session holds a transaction on the affected tables and report any measured wait. Read the generated SQL with alembic upgrade --sql and flag NOT NULL without a default, dropped or narrowed columns, type changes that rewrite tables, backfills inside the DDL transaction, and indexes created without CONCURRENTLY. Run alembic downgrade and upgrade again and say what the downgrade discards, run alembic check against the models, and run the test suite. Comment on the changed lines with evidence and a safer staged alternative where one exists, and return migration-review.md with every command, its output and the limits of what a fresh database can show. Do not edit the migrations or open another pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/42"
    }
  ]
}
  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

The pull request receives review comments on the specific lines of the new revision file: the op.alter_column that adds NOT NULL without a server default, the op.execute backfill that runs inside the DDL transaction, the op.create_index that would block writes, each with the SQL Alembic generated, what happened when the agent applied it with a concurrent transaction open, and a staged alternative in two or three migrations where that is safer.

migration-review.md comes back as an artifact on the session. It lists the revision ids and their down_revision, the alembic heads and alembic history output, the Postgres version used, the rows loaded, each command with its output in order, the measured waits, what alembic downgrade -1 restored and what it discarded, the alembic check result, and the test run. A limits section says what a fresh database cannot show, such as how long a rewrite takes on a table with millions of rows.

A reviewer reads the comments on the diff, opens the report for the commands behind them, and decides whether to merge, split or rewrite. The migrations themselves are untouched; the agent opens no pull request of its own.

Run it on every pull request that touches alembic/versions

In the console, add an automation to the Project with this prompt and a GitHub trigger on pull_request.opened and pull_request.synchronize. Only work by an owner, member or collaborator fires it, and pull requests from forks never do. Each push starts a run on the pull request's branch; a pull request that adds no file under alembic/versions ends with a short note and no comments.

Without the console, your own server can do the same: POST /o/{org}/sessions with the pull request URL in resources, one call per pull request, and a webhook_url on the key to learn when the review is posted.

The use case this job follows, with its automation and trigger: Review every pull request.

Why run it on Rush?

Migration review by eye finds the obvious drop_column and misses the lock. Applying the migration to a real Postgres with data in it, while another session holds a transaction, is what shows whether the ALTER TABLE waits, and that needs a database the reviewer does not want on their laptop. The sandbox starts the exact Postgres major the repository's compose file names, and the transcript keeps every command and wait.

The review is a comment on the line with the SQL and the measurement behind it, not a general warning about migrations. A reviewer who disagrees can replay the same commands from the report against a staging copy, and the pull request author gets a staged alternative they can apply instead of a request to be more careful.

Frequently asked questions

Does applying the migration to a fresh database prove it is safe in production?

No, and the report says so in its limits section. A fresh database shows the SQL, the lock behavior against a concurrent transaction, and whether the downgrade works; it cannot show how long a table rewrite takes on production-sized data. Comments that depend on size say so and name the table.

Can it tell what a downgrade loses?

Yes, by running it. After alembic downgrade -1 the agent compares the schema and the synthetic rows with what was there before the upgrade. The template's own fe56fa70289e downgrade drops the created_at columns, which is the kind of loss the comment names.

Will it rewrite a dangerous migration for me?

No. The prompt forbids editing the migrations or opening another pull request. The comment proposes the staged alternative in words and SQL, and the author decides. A follow-up run with a different prompt can open a pull request with the staged version if you want that.

The migrations are excluded from mypy and ruff. Does the review cover them?

Yes. In the template pyproject.toml excludes the alembic directory from both tools, which is one reason the review runs the migration rather than reading it. The agent also runs alembic check so a model change with no migration, or a migration that diverges from the models, is caught.