Can You Trust an AI-Generated Database Migration?

An agent can emit a migration that drops a column or rewrites a table in one step. Expand and contract keeps the old data until nothing still reads it.

By Muhammad Saif and Aashan Khan · Techtribe co-founders

An AI coding agent will write the migration that makes its new code compile. It will rename a column in place, drop the old one, add a non-null column with a made-up default, or rebuild a table because that was shorter than altering it. The migration runs. The previous version of the app, still serving traffic during the deploy, queries a column that no longer exists. Or the deploy fails and a generated "down" migration drops the data the up migration just copied. The code review looked fine because the new code and the new schema agree with each other. Production was still on the old agreement.

Runtime database errors, the red toast and the empty list, are in how to fix database errors in a vibe-coded app. This note is the schema change that causes the next ones, and the data you cannot get back by reverting a commit.

What the agent is optimizing for

The agent sees the types it just wrote and the migration tool's API. It does not see the other process still running, the replica, the report job, or the backup that was taken before the drop. A single migration file that takes the database from the old shape to the new shape in one step is the natural output. It is also the step that couples the schema deploy to the code deploy so tightly that you cannot roll either back alone.

Rename is the common case. The agent renames name to full_name in the table and in the code. During the rollout, old code still writes name. The column is gone. Writes fail, or a trigger you did not know about fails, or the old code writes nothing and you lose the field. A drop of an unused-looking column is the quiet case. The column was unused in the files the agent read and very used in a job the agent did not read.

A generated down migration mirrors the up migration in reverse. Up added a column and backfilled it. Down drops the column. If your pipeline runs down when a deploy fails, it deletes the backfill. The argument against automatic destructive down migrations is the right default: a down migration a machine may run unattended has to be non-destructive. If reversing the change would drop data or undo a transformation, the down migration should do nothing, and rollback should be "deploy the previous application image," which still understands the old columns because you have not dropped them yet.

Expand, then contract

The expand and contract pattern is the sequence that keeps the old structure alive until nothing needs it. Prisma's guide describes it as a way to move from one structure to another without taking the app down, and as a way to roll back through most of the process because you did not destroy the old data as you went.

For a column split, a rename, or a new table, the shape is:

  1. Expand. Add the new column or table. Leave the old one. Do not point reads at the new one yet. Existing code keeps working because you did not take anything away.
  2. Write both. Deploy application code that writes the old structure and the new one, and still reads the old one. New rows stay in sync. If this deploy is bad, roll back the application. The extra column sits unused. No data was destroyed.
  3. Backfill. Copy old rows into the new structure in batches, not in one transaction that locks the table for the length of your history. Check that the copy matches. A query that counts disagreements should return zero before you proceed. A batch that fails can be rerun. A single UPDATE of the whole table is how a migration becomes an outage even when the SQL is "correct."
  4. Read the new structure. Deploy code that reads the new column and still writes both. You can still roll the application back to the previous version, which reads the old column, and you have not stopped filling the old column, so the rollback is not missing writes.
  5. Contract. After a stretch of quiet, and after you have searched for every other reader, drop the old column or table. This is the point where rollback is no longer free. Do it in its own change, not in the same hour as the first expand. Prisma's guide notes that once reads have moved, a rollback can lose values that exist only in the new shape if you are not still writing the old one. That is why you keep the dual write until you are sure, and why the drop is last.

The agent can still help. Ask it for the expand step only. Review the SQL. Run the backfill yourself, or review the batch job as its own change. Do not ask it to "migrate the schema to match the code" in one shot on a database that has users.

Locks, copies, and the other ways a correct migration takes you down

Even an additive change takes a lock. If a long query holds a lock the migration needs, the migration waits, and every new query waits behind the migration. A metadata change becomes an outage. Set a lock timeout so the migration fails fast instead of queueing the product, and retry when the database is quieter. Failing fast is a success. Blocking checkout for ten minutes is not.

A default that rewrites every row is a different surprise. Adding a nullable column is usually cheap. Adding a column whose default must be computed per row can rewrite the table. Know which one the agent emitted. "It is just one ALTER" is not a size.

A migration that copies the production database into a laptop or a shared staging project, so the agent can "see real data," is a data leak and a second place the data can be corrupted. If you need realistic data, use a scrubbed copy with a purpose and a deletion date. The legal side of copies of customer data is in who is responsible when an AI-built app exposes customer data.

Backups matter here in a specific way. Take one immediately before the contract step, and know how to restore it. A backup from last night does not include today's orders. A restore onto the same database the agent can still migrate is how you drop the data twice. Recovery practice is in how to recover after a failed deploy.

What to look for in a generated migration

Read the SQL. Do not read the agent's summary of the SQL.

  • DROP COLUMN, DROP TABLE, TRUNCATE, DELETE without a where clause you understand, ALTER ... RENAME. These are contract steps. They do not belong in the first pull request of a change.
  • A rewrite of a table by creating a new one and swapping names in one file. Same effect as a drop, with more confidence in the diff.
  • A non-null column added without a default the existing rows can survive, on a table that already has rows. The migration fails, or it fills a lie.
  • A down migration that drops what the up migration added, wired to run on deploy failure.
  • A data transformation, a split of a string, a cast, applied in one statement with no count of rows that will not cast. Those rows become errors or silent nulls.
  • A migration committed next to application code that only understands the new shape, with no release that writes both.

If the diff is only ADD COLUMN nullable, plus application code that does not yet depend on it, you are in the expand step. That is the one to let through after a normal review.

What to check before it runs against production

  1. Run it against a copy first. Compare row counts and a checksum of the columns you care about, before and after.
  2. Confirm the application version that is live still works against the database after the migration. If it does not, you have coupled them. Split the change.
  3. Confirm there is a lock timeout, and that a failed attempt leaves the schema unchanged.
  4. Confirm the deploy pipeline will not run a destructive down migration on failure.
  5. Name the other readers: jobs, exports, the admin tool, the agent itself if it queries with a privileged role. The agent's database role should not be the role that can drop tables in production. That permission problem is in what the coding agent can reach.
  6. Take a backup you have restored at least once in a drill, then run the contract step in a window when someone is awake.
  7. Afterward, run the two-account check and the core write path. A migration that "succeeded" and broke row-level security is a security bug with a green migration log.

What to stop doing

Do not paste the production connection string into the agent so it can iterate on the migration. Do not accept a migration you cannot explain line by line because the ORM generated it. The ORM is not responsible for your data. The person who runs it is. Do not combine a feature and a drop in one deploy because the agent put them in one branch. Split the branch.

Do not keep a shadow database the agent uses for "experiments" that is actually a restored production snapshot with real emails in it. Scrub it or do not restore it.

A worked rename, the long way

Suppose the agent wants users.name to become users.full_name, and the table has years of rows. The one-step migration is ALTER TABLE users RENAME COLUMN name TO full_name, shipped with code that only knows full_name. For the minutes both versions run, or forever if a job was missed, something still says name.

The long way is five changes, each deployable. First, add full_name, nullable, and change nothing else. Second, ship code that writes both columns and reads name. Third, backfill in batches and stop when a count of rows where the two columns disagree is zero. Fourth, ship code that reads full_name and still writes both, watch it, and keep the previous build ready. Fifth, in a later change, stop writing name and only then drop it, after a search for every query, report, and agent prompt that still mentions the old column.

At steps one through four you can deploy the previous application and the data is still there. Step five is the one you schedule, back up, and do while someone is watching. The agent can draft the SQL for step one today. It should not draft steps one through five as a single file you run because the diff was large and the description sounded confident.

If the column is a key other tables reference, the long way also includes those foreign keys. Dropping the column first will fail or, worse, will succeed in a database where the constraint was never created. Check the constraints on a copy. The agent's migration often assumes the schema it inferred from the ORM, which is not always the schema that is live.

Roles, copies, and who is allowed to run it

The migration should run as a role that is not the application's everyday login and not the agent's laptop login. The application role can read and write rows. It cannot drop tables. The agent, if it has a database URL at all, gets a read-only role on a scrubbed copy. The person who applies the production migration uses a path you can audit: a pipeline log, not a pasted connection string in a chat.

Keep that copy scrubbed. Emails, names, and payment identifiers do not need to be present for you to see whether an ADD COLUMN locks the table. A restored production snapshot on a developer machine is a second production, with worse access control, and it is personal data sitting where the agent can read it into a prompt. If a bug truly requires a realistic distribution of values, generate that distribution. Do not export the customers.

Record the migration in the same history the database will enforce. Tools that record applied versions stop a migration from running twice. An agent that instead keeps a folder of SQL files "we run by hand" will run one twice and skip another. If the history table says a migration ran, and the schema does not match, believe the schema and fix the history with a person watching. Do not ask the agent to "sync" them by emitting whatever ALTER makes the types compile.

If this is your app, send the repo.

A 30-minute scope call. We read what is live, name what is dangerous, and quote a fixed scope.

How Techtribe reviews a migration before it runs

We read the SQL, split it into expand and contract if it is a single destructive step, and run the expand against a copy with a before-and-after check on the rows. The contract waits until the new code has been reading the new structure and the old one is still being written. We do not run an agent's one-shot migration against production to see what happens.

If the schema is already wrong and users are hitting errors, start with the live symptom and repair that path. If you are about to let an agent rename columns on a database that takes payments, the scope call should happen before the command runs. Bring the migration file and say whether it has already been applied. A Production MVP fits when the product needs a real data model, not a patch on the last generated alter. The packages page is the boundary between those.

The fixed scope for taking a generated app to production is a page per tool: Lovable, Bolt, v0, and Replit.

Questions

Why are AI-generated migrations risky?

The agent optimizes for the schema the new code expects, in one file, and it often emits a drop, a rename, or a rewrite. A live app still running the previous release will break, and a drop does not come back when you revert the deploy.

What is expand and contract?

Add the new structure beside the old one, write to both, backfill, switch reads, and only then remove the old structure. Prisma's data guide describes that sequence. Most steps can be rolled back by deploying the previous application, because the old columns are still there.

Should a failed deploy run the down migration automatically?

Only when the down migration does not destroy data. A generated down migration that drops a column you just backfilled will delete that data at the worst moment. Prefer a no-op down migration and roll back the application instead.

How is this different from a query that errors?

A query error is a request that failed. A bad migration is the table itself changing under every request. Fixing the query is covered in the database-errors note. This note is the change to the schema.

Book a free call

Have a vibe-coded app in production?

30-minute free call. We'll read what you have, name what is dangerous, and quote a fixed scope.