Most migrations that take a site down pass code review first. Here is the failure I kept seeing, why I decided to build a tool for it, and a running log of how it is going.
rayanstudio

You add an index. Two lines, one migration, and the diff reads exactly like the twelve before it.
BEGIN;
CREATE INDEX idx_orders_created_at ON orders (created_at);
COMMIT;A reviewer checks that the column is the right column, agrees the query needs an index, and approves. Then it runs against a table with 40 million rows in it. Postgres takes a SHARE lock on orders and holds it until the entire index is built. Every insert, update and delete on that table waits. Checkout waits. Requests pile up, connections run out, and by the time anyone looks at a dashboard the incident is no longer about one table.
Nothing in that diff was wrong. The reviewer was not careless. The information needed to catch it lives in the Postgres locking documentation, and review is not when anyone reads it.

Reviewers read intent. Lock behaviour is not in the diff, and it shifts with the statement, the Postgres version, and whether the tool wrapped everything in a transaction.
CREATE INDEX blocks writes. CREATE INDEX CONCURRENTLY does not, but it fails outright inside a transaction, and most migration tools open one by default. That is three facts you have to hold at once to review two lines correctly, for one of the dozens of statements that behave this way.
Teams patch over it with tribal knowledge: the senior engineer who remembers, the wiki page nobody opens, the checklist that works until its author leaves. That is a job for a tool.
Four categories cover almost everything I have seen.
The statement takes a lock that blocks reads or writes for as long as the work takes. An index built without CONCURRENTLY. A foreign key added without NOT VALID, which blocks writes on both tables. A CHECK constraint validated on the spot. SET NOT NULL, which scans the whole table while blocking every query against it.
The statement does not just change metadata, it copies the table. Changing a column type does this. So does adding a column with a volatile default such as gen_random_uuid(). On a small table nobody notices. At 50 GB it holds ACCESS EXCLUSIVE and the table is unavailable until it finishes.
During a rolling deploy the old pods and the new pods serve traffic at the same time. Drop a column the old code still selects and those pods throw errors until they are replaced. Rename one and both halves break, in opposite directions. The migration is correct on its own. The order it ships in is what is wrong, and no tool that only reads SQL can see that.

CONCURRENTLY inside a transaction fails. Validating a constraint in the same transaction that added it NOT VALID holds the exact lock you added NOT VALID to avoid. Locking DDL without a lock_timeout means that when the lock cannot be taken, every query behind it queues instead of failing fast.
migrail reads migrations the way the database will run them and says what will happen before you merge. I set four commitments on the first day and I am holding to them.
I checked before writing code, which felt more responsible than checking after. Everything below is as I found it on 17 September 2026, and these things move.
Atlas does schema management well and its lint engine includes Postgres lock checks. Since v0.38 in October 2025, atlas migrate lint is Pro only in the official binary and needs atlas login, and every Postgres lock and rewrite check is marked Pro. Lint also wants a dev database to run against.
squawk is the closest free tool and it is genuinely good: a real Postgres parser, 42 rules, a GitHub Action, an editor extension. It is also honest about its scope. Its documentation says it has no special support for Django or any other web ORM and suggests piping generated SQL in instead. No application code awareness, no table sizes.
strong_migrations is excellent, Rails only, and checks when the migration runs rather than when you open the pull request. django-pg-zero-downtime-migrations covers Django on Postgres by changing how migrations execute instead of reviewing them. pgroll and Reshape are executors, a different category, and ask you to adopt their migration format.
So the gap is not that nobody has written a Postgres linter. The gap is one that is free, speaks your framework, knows your code, and can prove its claims.
This is the part I care most about, and the part I would skip if I were in a hurry.
migrail’s lock model is data: for each statement shape, which lock mode Postgres takes, what that blocks, whether it rewrites the table, and how any of that changes by version. The obvious way to fill that in is to read the documentation and type it up. The problem is that you cannot tell the difference between a model that is right and one that is confidently wrong.
So the harness verifies it. It starts PostgreSQL 12, 13, 14, 15, 16, 17 and 18 in Docker, runs each statement against a real table, reads back from pg_locks what the server actually took, and compares that with the prediction. Any disagreement fails the build. make lockverify runs it locally and CI runs it nightly.
It caught claims I had got wrong from reading alone. It also means that when a new Postgres major version changes behaviour, the harness tells me before a user does.
v0.1 is out: PostgreSQL 12 to 18, 25 rules, and nine migration tools detected without configuration. It reads each tool’s own conventions, so goose NO TRANSACTION, dbmate transaction:false and Atlas atlas:txmode all mean what they should. By default it only checks migrations that are new or changed against your base branch, including files you have not committed yet.
It installs as a single binary on macOS, Linux and Windows through Homebrew or a checksum-verified script.

I will add an entry here as each milestone lands, so this page stays a record of how the thing was actually built rather than a description written afterwards.
Foundations, cross-platform builds and CI first. Then the analysis engine on the real Postgres parser with the first rules and JSON output. Then the terminal design: colours, code frames, explain and rules. Then git-aware change detection, inline ignores that require a reason, the config file with a JSON schema, and the lock verification harness. Released with GitHub releases, a Homebrew tap and an install script.
Two things I would call out from that day. Parsing only the changed migrations instead of everything, and cutting the number of git calls, mattered more for how the tool feels than any rule did. And the code reviews at the end of each milestone found five or six real issues every time, which is an argument for reviewing your own work in a separate pass rather than trusting the pass that wrote it.
v0.2 is framework adapters for Django, Alembic, Rails, Laravel, EF Core, Knex, TypeORM, Sequelize and Liquibase, a capture mode for everything else, CI integrations and the remaining package managers. v0.3 is the application code scanner, live impact estimates from a read-only staging connection, and an interactive explorer. v1.0 is the full catalogue with stable IDs.
If you run migrations against a database with real users behind it, the most useful thing you can send me is a repository where migrail gets it wrong. The source is on GitHub.