{"slug": "what-40-agent-written-migrations-got-wrong", "title": "What 40 agent-written migrations got wrong", "summary": "A developer ran 40 first-draft Postgres migrations generated by a coding agent through an 18-rule safety linter, finding 13 clean files, 25 warnings, and 2 critical flags. The agent reliably avoided the classic NOT NULL DEFAULT pitfall and produced syntactically valid SQL, but consistently chose locally optimal forms that block production, such as plain CREATE INDEX instead of CONCURRENTLY and SET NOT NULL without a prior validated CHECK constraint. The two critical flags were a TRUNCATE and a VACUUM FULL that the agent executed as literally requested without pushback.", "body_md": "A small experiment. We gave a current coding agent the role of \"helpful assistant on a Postgres web app\" and asked it, in forty separate requests, for the migrations a team writes in a normal quarter: add a column, add an index, add a foreign key, change a type, rename, drop, backfill, truncate, vacuum. No hints about safety, no linter in the loop, first draft only. Then we ran the eighteen rules the free CLI bundles over the forty files.\n\n| Result | Files | \n|---|---|\n| Clean | 13 | \n| Flagged, warning | 25 | \n| Flagged, critical | 2 | \n| Flagged, note only | 1 | \n\n| Rule | Files | What it is | \n|---|---|---|\n| [BV003](https://bolvrk.com/rules/bv003) | 10 | Index created without CONCURRENTLY | \n| [BV004](https://bolvrk.com/rules/bv004) | 4 | Column type change that rewrites the table | \n| [BV005](https://bolvrk.com/rules/bv005) | 4 | Rename or drop inside the deploy window | \n| [BV011](https://bolvrk.com/rules/bv011) | 4 | SET NOT NULL scanning the table under an exclusive lock | \n| [BV008](https://bolvrk.com/rules/bv008) | 2 | Foreign key added without NOT VALID | \n| [BV013](https://bolvrk.com/rules/bv013) | 1 | TRUNCATE in a migration | \n| [BV015](https://bolvrk.com/rules/bv015) | 1 | VACUUM FULL under a full lock | \n| [BV014](https://bolvrk.com/rules/bv014) | 1 | Unbounded UPDATE | \n| [BV016](https://bolvrk.com/rules/bv016) | 1 | DROP INDEX without CONCURRENTLY | \n\nThis is the part worth reading before the part about what it got wrong. Every time the agent was asked for a required column, it added a default: `ADD COLUMN status text NOT NULL DEFAULT`, \n\n  'open'`ADD COLUMN email_verified boolean NOT NULL DEFAULT false`. That is the correct modern form, it is instant on Postgres 11 and later, and [BV002](https://bolvrk.com/rules/bv002) never fired once. The single most famous migration mistake did not appear. Models have read a lot of blog posts about that one.\n\nIt also wrote a correct `USING` clause on every type change, used `string_to_array` sensibly for the tags split, and wrote a clean `CREATE TABLE` with a foreign key when asked for a new table. The SQL was never malformed. Not one of the forty files had a syntax error.\n\nTen of the forty files create an index, and all ten do it the plain way. Not one `CONCURRENTLY`. On a dev database the plain form is faster and simpler, and the agent has no reason to prefer the form that only matters when the table has fifty million rows and writes going to it. This is the shape of most of the flagged files: the agent picks the form that is *locally* best, and the form that is locally best is the one that blocks production.\n\nThe four `SET NOT NULL` statements are the same story. The agent even did the right thing first, backfilling with an UPDATE before setting the constraint, and then set the constraint the way that scans the whole table under an exclusive lock, because that is the short way to write it. The two foreign keys were added without `NOT VALID`, so they validate every existing row while holding locks on both tables. The pattern is consistent enough that you could predict which files would be flagged from the request alone.\n\nThe two criticals were a `TRUNCATE` and a `VACUUM FULL`, both of which were literally what was asked for. That is a fair point in the agent's defence and it is also the point: the request was reckless, the agent did not push back, and the only thing standing between the request and production was whether anyone was checking.\n\nEvery flagged file has a one or two line correction. The four that account for twenty of the twenty-seven:\n\n``` php\n-- BV003: the agent wrote            ->  the fix\nCREATE INDEX idx ON orders (customer_id);\nCREATE INDEX CONCURRENTLY idx ON orders (customer_id);\n\n-- BV011: backfilled, then\nALTER TABLE users ALTER COLUMN email SET NOT NULL;\n-- fix: prove it under a weak lock first, then the SET NOT NULL is a catalog change (PG12+)\nALTER TABLE users ADD CONSTRAINT users_email_nn CHECK (email IS NOT NULL) NOT VALID;\nALTER TABLE users VALIDATE CONSTRAINT users_email_nn;\nALTER TABLE users ALTER COLUMN email SET NOT NULL;\nALTER TABLE users DROP CONSTRAINT users_email_nn;\n\n-- BV004: a type change rewrites the table under ACCESS EXCLUSIVE\nALTER TABLE orders ALTER COLUMN total TYPE bigint;\n-- fix: new column, batched copy, swap, drop (expand / migrate / contract)\nALTER TABLE orders ADD COLUMN total_new bigint;\nUPDATE orders SET total_new = total WHERE total_new IS NULL AND id BETWEEN 1 AND 50000;  -- repeat\n-- ... switch the application, then rename and drop in a later release\n\n-- BV008: validates every row while locking both tables\nALTER TABLE payments ADD CONSTRAINT payments_order_fk FOREIGN KEY (order_id) REFERENCES orders (id);\n-- fix: record now, validate under a lock that lets writes through\nALTER TABLE payments ADD CONSTRAINT payments_order_fk FOREIGN KEY (order_id) REFERENCES orders (id) NOT VALID;\nALTER TABLE payments VALIDATE CONSTRAINT payments_order_fk;\n```\n\nThirteen of forty clean is not a verdict on the agent. It is a measurment of what \"looks right\" means when the thing that makes a migration wrong is a table the author will never see. The fix is not a better prompt. It is the same check, run the same way, on every file, before it merges.\n\n*Originally published at [bolvrk.com](https://bolvrk.com/blog/what-40-agent-written-migrations-got-wrong). Bolvrk is a deterministic checker for database migrations and committed credentials: [free CLI, GitHub App and Action](https://bolvrk.com/?utm_source=crosspost&utm_medium=blog).*", "url": "https://wpnews.pro/news/what-40-agent-written-migrations-got-wrong", "canonical_source": "https://dev.to/bolvrk/what-40-agent-written-migrations-got-wrong-4go5", "published_at": "2026-09-17 15:30:03+00:00", "updated_at": "2026-09-17 15:53:07.569699+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["Postgres", "bolvrk.com"], "alternates": {"html": "https://wpnews.pro/news/what-40-agent-written-migrations-got-wrong", "markdown": "https://wpnews.pro/news/what-40-agent-written-migrations-got-wrong.md", "text": "https://wpnews.pro/news/what-40-agent-written-migrations-got-wrong.txt", "jsonld": "https://wpnews.pro/news/what-40-agent-written-migrations-got-wrong.jsonld"}}