All jobs

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

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

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

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.