I hand a lot of work to coding agents now. Scaffolding, test fixtures, the first pass at a refactor I have been avoiding, the tedious 200-line change that would have eaten my afternoon. I am not precious about it. But there is one job I still do with my own hands every single time, and I do not see that changing soon. AI agent database migrations sound fine in a demo, where the schema is empty and nobody is watching. Run the same thing against a table with four years of real rows in it and the failure is quiet, expensive, and sometimes not reversible. So the agent can write the migration. It does not get to run it.
I have spent a lot of years on backends that had to stay up under real load, from a DeFi exchange to an e-commerce app doing serious numbers on the Play Store. The pattern that scares me is always the same. Everything an agent touches feels safe because you can undo it. A migration breaks that promise.
A migration changes data you cannot get back
Almost everything a coding agent writes is safe to be wrong, because you catch it before it matters. A bad function fails a test. A broken component shows up in review or gets reverted with one deploy. The blast radius is your code, and code is cheap to roll back. A migration is different in kind. When it drops a column, rewrites a type, or backfills a few million rows, it changes the data itself. Reverting the migration file does not bring the deleted rows back. You are into restoring from a backup, if you have a recent one, and hoping you did not lose the writes that landed in between.
That is the line I care about. The agent operates in a world where mistakes are reversible, so it treats every step as reversible. Migrations live on the other side of that line, and nothing in the agent's training tells it which side it is standing on.
Where the agent actually goes wrong
This is not theory. These are the failure modes I have watched agents produce, all of which passed locally on a table with a dozen rows.
- Locking a big table without knowing it. An agent adds a column, sets a default, adds a NOT NULL, all in one step. On a small table that is instant. On a busy table that can hold a lock long enough to stall every write behind it, and the agent never feels the 40 seconds of stalled traffic that causes because there was no traffic in its sandbox.
- Backfilling inside the schema change. It does the column add and a full-table
UPDATEin a single transaction, so the lock is held for the entire backfill instead of a moment. The bigger the table, the longer the whole app waits. - A down migration that lies. It writes a tidy-looking rollback for a step that dropped data. The down migration runs without error and restores nothing, because the data it needed is already gone.
- Renaming a column the running app still reads. You ask it to rename
user_nametousername, it writes exactly that, and the second the migration lands the old app version throws on every request until the new deploy catches up.
The expand and contract habit an agent skips
The safe way to change a column that is in use has been known for years. You expand first: add the new column as nullable, deploy code that writes to both old and new, backfill the old rows in small batches so you never hold a long lock. Once the new column is fully populated and the app reads from it, you contract in a later deploy by dropping the old one. That is three or four deploys spread over days, done carefully, watching row counts as you go.
Ask an agent to rename a column and you get a single rename statement, because that is literally what you asked for. It is not wrong so much as it answers a different question than the one that keeps you online. The gap between what you asked and what is safe under load is exactly the gap where an agent hurts you, and it hurts you silently, which is the part I hate. This is the same reasoning I used when I wrote about idempotent tool calls: give an agent an action it can repeat or reverse, and it is fine, take that safety away and you need a human in the loop.
So what do I actually let the agent do
Plenty, as it turns out. I am not arguing to keep agents away from the database. I am arguing about who pulls the trigger.
- Draft the migration from a plain description. It is good at the boilerplate and the syntax I always forget.
- Write the batched backfill script. Fine, and I read every line before it runs anywhere near real data.
- Tell me, honestly, whether the down migration truly reverses the up. Asking it to reason about that out loud catches the fake rollbacks early.
Then I run it. On a copy of production first, watching locks and timings, then on the real thing during a quiet window with a fresh backup behind me. The agent got me most of the way there and saved me the boring part. It just does not get to be the one standing at the one-way door when it opens. If you are letting agents write code that touches anything sensitive, the wider version of this argument is in my note on securing AI-generated code.
Match the autonomy to the blast radius
That is the whole rule I keep coming back to. A wrong React component costs you a redeploy. A wrong migration can cost you the data, and no amount of clever prompting changes the fact that some actions are cheap to undo and some are not. So I let agents run free on the reversible work and I keep my hands on the parts where the undo button is a lie. I learned this the durable way, keeping an e-commerce backend healthy through real spikes, which I wrote about in the Aftertutor case study. If you want more of how I think about agents on real systems, the rest of it lives in my AI and agents notes, and the honest version of where these tools earn their keep is here.
Related in AI
Putting agents to work on a real codebase?
I am open to senior full-stack, backend, or Web3 engineering roles, fully remote and any timezone. If the hard part is using these tools without breaking production, that is the work I like.
Get in touch →