Founder pricing: Pro at $9.99/mo forever for the first 50 members. 18 of 50 left Claim yours →
All posts

Database migrations with AI coding agents: fine in dev, a lock in production

The SanuDesk team 6 min read
database migrations ai agents

The task was small: add a status column to the orders table, default it to pending, backfill the rows that predate the feature. The agent's diff was four lines. The migration ran clean against the seed data, and the test suite went green in under a second. It reads like the safest change you shipped all week.

Then it runs against the real orders table: eleven million rows, live traffic all day. Adding a column with a default can rewrite every row in place, and depending on your database and version, reads and writes on that table block until the rewrite finishes. The four-line diff that looked trivial in review just became your outage.

This isn't the same problem as migrating a large codebase to a new framework version. That's a file-by-file rewrite you can checkpoint and roll back branch by branch. A database migration runs once, against live data, and the rollback, if one even exists, has to reverse something that already happened to rows a real user is reading right now.

Why a correct migration can still be dangerous

A migration can be functionally correct, the SQL does exactly what you asked, and still be dangerous, because correctness and locking behavior are different questions. An agent, and most test suites, only ever answers the first one.

strong_migrations, an open-source gem built specifically to catch this gap, documents the patterns worth knowing even if you never install it. Adding a column with a default value used to force a full table rewrite on Postgres, MySQL and MariaDB alike. Postgres 11, MySQL 8.0.12 and MariaDB 10.3.2 all optimized the plain case, but the safe path breaks again the moment the default is anything volatile, like a function call. Adding an auto-incrementing column, changing a column's type, or adding a stored generated column still force a full rewrite today, and reads and writes block in Postgres for as long as it takes. Adding a check constraint blocks reads and writes while it validates every existing row against the new rule.

None of that shows up in a review of the diff. It only shows up against a table the size of your real one.

Migration patterns that read fine and lock a table

Renaming a column or a table. The migration succeeds instantly. Every process that still references the old name, including another agent session mid-task, or an app instance one deploy behind, starts throwing errors the moment the rename commits, not when the code finally catches up.

Removing a column. Plenty of ORMs cache the schema they saw at boot. A drop that looks isolated to one migration file can break any process that hasn't restarted yet.

A down() that isn't real. Ask an agent to "add a rollback" and you can get a down migration that runs without error and still doesn't reverse the change, for example dropping a column entirely when the up() only changed its default. The rollback exists in the file, not in reality.

A backfill written as one giant update. The output is correct and the execution is the problem: a single statement touching every row holds its lock for as long as the update runs, and on a large table that's minutes, not milliseconds.

Expand, migrate, contract: split the one-way door into three

Fowler's parallel change pattern predates AI agents by a decade and solves exactly this. Instead of one step that breaks the old shape the instant it runs, you get three: expand (add the new column or index alongside the old one, nothing removed yet), migrate (move readers and writers over while both shapes still work), and contract (drop the old shape once nothing references it anymore).

Applied to the earlier rename: instead of asking an agent to rename a column in one step, ask for three separate, reviewable diffs. Add the new column as a copy. Update the code to write both and read the new one. Drop the old column in a later migration once you've confirmed nothing still needs it. Each step is small enough to read in a minute, which is exactly the kind of one-way door plan mode exists to gate before it runs.

Where AI-written migrations go wrong, and what it really costs

Dev data hides the cost. A migration tested against a seed database with a hundred rows tells you almost nothing about what the same statement does against a hundred million. A green suite doesn't mean a safe migration, it means the SQL ran once, quickly, on data too small to reveal a lock.

Staging usually isn't a fair test either. Unless staging is seeded at production scale, which costs real storage and real anonymization work, "it ran fine in staging" mostly proves the syntax is valid.

Parallel agents collide on shared migration history. Two sessions generating migrations against the same dev database is a different coordination problem than a code merge conflict: the files can merge cleanly in git and still apply in the wrong order, or claim the same version.

A lock during business hours is an outage with a different name. Nobody wrote a bug. The migration did exactly what it said. The cost shows up as a queue of blocked queries and a support channel filling up, not as a stack trace.

Give the migration a review lane before it touches anything real

SanuDesk is a desktop app built around exactly that gap between a diff that reads fine and a change that's safe to run. Your Claude Code, Codex and Gemini sessions tile into one grid, each scoped to its own project folder, so two agents writing migrations at the same time are panes you watch side by side instead of one shared terminal you're guessing about. Work arrives through a Kanban board: "add the status column" becomes a card, it deploys to a fresh session, and once the migration is written, the card parks in a review lane with the diff attached, one more checkpoint before it runs anywhere that matters. Because SanuDesk is bring-your-own-model, that review can run on a different model than the one that wrote the migration, cheap insurance for a one-way door. A recurring sweep, checking new migration files against the patterns above before they merge, becomes a Loop, journaled per run instead of a step everyone means to remember. The grid and board are in the free plan (see pricing).

Check the row count before you write the next one

Pull every migration file your agents wrote in the last two weeks. For each one, answer two questions the test suite never asked: how many rows does this table actually hold in production, and does the down() genuinely reverse the up(), or does it just run without erroring.

If you don't know the row count, that's the finding. You've been trusting migrations against a number you've never checked. Get the count, then decide whether the change belongs in one step or three.

Download SanuDesk free to give every migration a review lane before it runs, or see how the grid, the board and Loops fit together on the features page.

Run your own fleet of agents

SanuDesk is a free desktop app that tiles your Claude Code and Codex sessions and gives every project a Kanban board your agents work from.