An agent that writes a migration is usually good at producing SQL that says what you meant. It adds the column, creates the index, backfills the field, adds the constraint. The statement parses, the table ends up with the right shape, and in a test database with a few thousand rows it finishes in milliseconds. Everything a language model can see looks fine.
What it cannot see is the state of your production tables. The failure mode that matters is not a wrong migration. It is a correct one that needs a strong lock and cannot get it, so it waits, and while it waits every other statement on that table waits behind it.
Two facts make this happen. First, most ALTER TABLE forms take an ACCESS EXCLUSIVE lock, the most aggressive table lock PostgreSQL has, which conflicts with all other lock modes including the ACCESS SHARE that a plain SELECT takes; the lock level is the strictest one required by any subcommand in the statement (PostgreSQL explicit locking docs). Second, PostgreSQL queues lock requests in order. A migration waiting for its lock blocks later requests that conflict with it, even though those later requests do not conflict with the long query actually holding the lock (the Xata writeup on the lock queue).
The measurable version of this is worth seeing once. In a controlled run on PostgreSQL 18.6, a report query held ACCESS SHARE inside an open transaction, a migration waited on it, and a separate reader queued behind the migration, with pg_blocking_pids() returning the migration's PID rather than the report's. A query on an unrelated table returned immediately (the pg_locks walkthrough). That is the whole outage in four rows: nothing was locked for long, and the table was still unavailable.
A human reviewer reading the SQL cannot see this either, because the cause is a concurrent session on production, not the text. That is why the review has to move from the text to measured behavior.



