# Review Database Migrations in an ASP.NET Core Pull Request

Yes. An agent checks a pull request's SQL scripts and EF migrations against each other, runs them on fresh databases and posts a review citing each file.

[Run this on Rush](https://cloud.getrush.ai/run/review-database-migrations/aspnet-core/), in a Project that has your repository.

## What the agent does

The agent reads the pull request, then the migration layout it has to satisfy. In an ASP.NET Core server shaped like bitwarden/server there are two systems to keep in step. SQL Server changes are plain scripts under `util/Migrator/DbScripts/`, 543 of them from `2017-08-19_00_InitialSetup.sql` onward, applied by `DbMigrator.cs` through DbUp inside a transaction and journaled to the `dbo.Migration` table; the canonical schema sits in the `src/Sql` database project, split under `src/Sql/dbo/` into domain folders such as `Vault`, `Auth` and `AdminConsole`, each with its own `Tables` and `Stored Procedures`. The other three providers, PostgreSQL, MySQL and SQLite, use Entity Framework migrations under `util/PostgresMigrations/Migrations/`, `util/MySqlMigrations/Migrations/` and `util/SqliteMigrations/Migrations/`, 334 files for PostgreSQL and MySQL and 290 for SQLite, each set ending in a `DatabaseContextModelSnapshot.cs`, generated from the `DatabaseContext` in `src/Infrastructure.EntityFramework/Repositories/` with its 79 `DbSet` properties.

The first checks are the ones `dev/verify_migrations.ps1` and the workflows encode. A new SQL script must be named `YYYY-MM-DD_NN_Description.sql` and sort after every existing script; an EF migration must be `YYYYMMDDHHMMSS_Description.cs` with its `.Designer.cs` beside it and a timestamp later than the newest one. `protect-files.yml` labels a pull request `DB-migrations-changed` when it modifies an existing script rather than adding one, because a script already journaled on production never runs again. The agent reads the diff with those rules in hand and then asks the questions the scripts cannot: does the SQL script guard its own re-run, as `2026-10-06_00_AddPamSeatMinimumToOrganization.sql` does with `IF COL_LENGTH(...) IS NULL` before `ALTER TABLE` and `CREATE OR ALTER PROCEDURE` for the procedure it recreates; does every stored procedure that selects or inserts the changed column get recreated; and does the matching EF migration, such as `20261014151657_AddPamRotation.cs` creating `PamAccessConnector` with its foreign keys to `ApiKey` and `Organization`, produce the same shape across all three providers.

Then it runs what `.github/workflows/test-database.yml` runs. It starts the databases from `dev/docker-compose.yml` with the `mssql`, `postgres` and `mysql` profiles, restores the tools with `dotnet tool restore`, runs `dotnet ef migrations has-pending-model-changes` in each of the three EF projects to prove the snapshot matches the model, applies the SQL Server scripts with `dotnet run --project util/MsSqlMigratorUtility/` on the pull request branch, and runs `dotnet ef database update` for PostgreSQL, MySQL and SQLite. The `--dry-run` option of the migrator utility lists the scripts DbUp would apply, which is how the agent confirms only the new script is pending. Finally it runs `test/Infrastructure.IntegrationTest`, whose `DatabaseDataAttribute` reads `BW_TEST_DATABASES__n__TYPE` and `CONNECTIONSTRING` variables and executes each repository test once per configured provider.

The review it posts is specific to migrations: whether the script is idempotent and transaction-safe, whether a column added as `NULL` with a later backfill follows the transition and finalization split the `DbScripts_transition` and `DbScripts_finalization` folders and the `_move_edd_db_scripts.yml` workflow exist for, whether the Dapper repository under `src/Infrastructure.Dapper` and the EF repository both read the new column, and what the integration tests said per provider. It comments; it does not approve, request changes on behalf of a person, or push.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Review the database migrations in this pull request. Read every changed file under util/Migrator/DbScripts, util/PostgresMigrations, util/MySqlMigrations, util/SqliteMigrations, src/Sql and src/Infrastructure.EntityFramework, and the entity classes they touch. Check the naming and ordering rules in dev/verify_migrations.ps1, that no already-applied script was modified, that each SQL script is safe to re-run and recreates every stored procedure that references a changed column, and that the three Entity Framework migrations match each other and the model snapshot. Start the databases from dev/docker-compose.yml, run dotnet ef migrations has-pending-model-changes in each EF project, apply the SQL Server scripts with util/MsSqlMigratorUtility (dry-run first), run dotnet ef database update for the other providers, and run test/Infrastructure.IntegrationTest with BW_TEST_DATABASES variables set for each database you started. Post one review comment on the pull request: a summary, then findings with file and line, what breaks and what to change, then the commands you ran with their results per provider. Do not approve or request changes, do not push, do not edit the branch.",
  "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 one review comment on the pull request. It opens with a summary of what the migration does in both systems, then lists findings with a path such as `util/Migrator/DbScripts/2026-10-15_00_AddCipherUpdateData.sql` and a line, the consequence on an already-migrated database, and the change that would fix it. A closing section records each command, `has-pending-model-changes` per provider, the migrator dry run, the four `database update` runs and the integration test results, with the output as it appeared in the sandbox.

The session read names the pull request and the comment, and the transcript at `GET /o/{org}/sessions/{id}/messages` holds every tool call, so the author can see which script the agent applied first and which test failed on which provider. The branch is untouched: the agent has no permission to approve, and the prompt tells it to post a comment and nothing else.

Before merging, a reviewer confirms the finding list against the diff, checks that a stored procedure flagged as not recreated really does read the changed column, and decides whether a two-phase change belongs in `DbScripts_transition` rather than in the main folder.

## Run it on every pull request that changes a migration

Add an automation to the Project with this prompt and a `github` trigger on `pull_request.opened` and `pull_request.synchronize`. Only pull requests by an owner, member or collaborator fire it, never ones from forks. The trigger adds the pull request event after the prompt, so the agent reads the number from it; a pull request that touches none of the migration folders gets a short comment saying there was nothing to review, and a label filter in the trigger's `config.labels` such as `DB-migrations-changed`, the label `protect-files.yml` already applies, keeps the automation to the pull requests that need it.

From your own server, the same run starts from a `POST /o/{org}/sessions` with the pull request URL in `resources` (`https://github.com/<owner>/<repo>/pull/<n>` clones that branch) and a `request_id`, one UUID kept per head commit, so one review per push. Give the key a `webhook_url` and the `run.completed` event tells your handler when the comment is up.

## Why run it on Rush?

Reviewing a Bitwarden migration properly means a SQL Server container, PostgreSQL, MySQL, MariaDB and SQLite, the EF tools, a fresh `vault_dev` on each, and the integration suite run against all of them. On Rush that stack starts inside the run's own sandbox, as `test-database.yml` does in CI, and every output is kept with the session, so the reviewer reads results rather than reproducing them.

Several pull requests get reviewed at the same time, each in its own sandbox with its own databases, and the comment on each one cites the file and line behind every claim. A migration that would fail on MySQL but pass on PostgreSQL is reported as exactly that, with the error from the provider that failed.

## Frequently asked questions

### Does it need the SQL Server image?

Yes, for the SQL Server side: `DbMigrator.cs` connects with `Microsoft.Data.SqlClient` and `MsSqlMigratorUtility` applies the scripts through DbUp. The sandbox starts the `mssql` profile from `dev/docker-compose.yml` next to PostgreSQL and MySQL; if a container cannot start, the comment says which provider was not exercised instead of implying it passed.

### What if the pull request changes only the EF side, or only the SQL script?

That is a finding. Every schema change needs the SQL Server script and the three EF migrations, and `has-pending-model-changes` fails in the EF projects when the model moved and the snapshot did not. The comment says which half is missing.

### Will it approve the pull request?

No. It posts one comment and leaves approval to a person. The prompt forbids approving, requesting changes and pushing, and the sandbox has only the permissions the Rush GitHub App installation grants on that repository.

### What does it say about a modified existing script?

That it will not run anywhere the script is already journaled in `dbo.Migration`, which is what `protect-files.yml` labels for. The review asks for a new dated script instead and points at the naming rules in `dev/verify_migrations.ps1`.
