MeatButton

Your Database Migration Failed. Here's What That Actually Means.

For anyone whose database changes broke their app

You changed something in your app. Maybe you added a field. Maybe AI rewrote half your code. You ran a deploy or a migration command, and now you're staring at an error like:

ERROR: column "email" of relation "users" already exists
ERROR: relation "orders" does not exist
django.db.utils.OperationalError: no such table: accounts_profile
Error: P3009 migrate found failed migrations

Your app won't start. The AI keeps suggesting things that make it worse. You're not sure if you're about to delete all your data.

Let's slow down and explain what's actually going on.

What migrations are

Think of your database as a building. When you first set it up, you draw blueprints and build the rooms. Each room is a table. Each wall and fixture is a column — "name," "email," "created_at," things like that.

Now your app evolves. You need a new room. Or you need to knock out a wall and combine two rooms. You can't just redraw the blueprints and pretend the old building doesn't exist. You need a renovation plan — step-by-step instructions that say "add this wall here, remove that door there, install plumbing in room 3."

That's what a migration is. It's a set of instructions that takes your database from one shape to another. "Add a column called phone_number to the users table." "Create a new table called invoices." "Remove the legacy_status column."

Each migration is numbered and run in order. Migration 001 creates the initial tables. Migration 002 adds a column. Migration 003 creates a new table. Your database keeps track of which migrations have been applied, so it knows where it left off.

Every major framework has its own migration system:

They all do the same thing: apply numbered changes to your database in order.

What "migration failed" means

A migration fails when the database is in a different state than the migration expects.

Go back to the renovation analogy. Your renovation plan says "install a door in the east wall of room 3." But someone already installed a door there last week. Or room 3 doesn't exist yet because migration 002 was supposed to build it and never ran. Or someone knocked down the east wall yesterday without telling anyone.

The contractor (your migration tool) shows up, looks at the building, looks at the plan, and says "this doesn't match. I can't do this." That's a failed migration.

The most common version of this: your migration says "add a column called email" but that column is already there. Or it says "modify the orders table" but that table doesn't exist yet. The database and the migration file disagree about what the database looks like right now.

Why AI makes this so much worse

This is where things go off the rails for a lot of people. If you're building with ChatGPT, Claude, Cursor, Bolt, Lovable, or any AI coding tool, you're especially likely to hit migration problems. Here's why.

AI regenerates your schema from scratch

When you ask AI to "add a phone number field," it doesn't carefully write a migration that adds one column. It often regenerates your entire database schema — every table, every column, every relationship — as if it's starting from zero. This creates a migration that tries to create tables that already exist, add columns that are already there, and set up things that were set up three migrations ago.

AI creates conflicting migrations

You asked AI for a change on Monday. It created migration 004. On Tuesday you asked for another change. It created a new migration 004 — same number, different content. Or it created migration 005 that contradicts what migration 004 just did. Now your migration history is a mess, and the tool doesn't know which path to follow.

AI doesn't understand migration history

Your migration system has a ledger — a record of every migration that's been applied. AI doesn't read that ledger. It doesn't know that migration 003 already added the status column. It doesn't know that you renamed users to accounts in migration 006. It writes instructions based on what it thinks the database should look like, not what it actually looks like.

AI suggests "just reset the database"

When migrations get tangled, AI's favorite suggestion is to wipe the database and start fresh. In development, that's fine. In production — where your actual users' actual data lives — that suggestion would destroy everything. AI doesn't distinguish between these two situations. It gives the same advice either way.

The dirty database

There's another common cause that doesn't involve AI at all: someone made changes directly to the database.

Maybe a developer logged into the database and added a column by hand because it was urgent. Maybe someone ran a SQL script to fix a bug. Maybe a different tool or plugin modified a table. The changes worked, but nobody created a migration file to match.

Now the database has a column (or table, or index) that the migration system doesn't know about. The next time migrations run, they try to create something that already exists, or they assume a shape that doesn't match reality.

This is called a "dirty" database, or migrations being "out of sync." The database has drifted away from what the migration files describe, and neither side knows it until something breaks.

The error messages and what they mean

"Column already exists"

ERROR: column "phone" of relation "users" already exists

A migration is trying to add a column that's already there. Either a previous migration already added it, someone added it manually, or AI generated a migration that re-creates existing columns.

"Relation does not exist"

ERROR: relation "orders" does not exist

A migration is trying to modify or reference a table that hasn't been created yet. Either an earlier migration was skipped, failed partway through, or the migrations are running out of order.

"Migration has already been applied"

Migration 20240315_add_users is already applied

The migration system's ledger says this migration already ran, but the migration tool is trying to run it again. This usually happens when migration files were deleted and recreated, or when AI regenerated the migration folder.

"Pending migrations" / "Migration state is not in sync"

Error: P3005 The database schema is not empty
Prisma Migrate could not resolve the migration history

The migration tool can see that the database has stuff in it, and it can see migration files that haven't been applied, but it can't figure out the relationship between the two. The database and the migration history have diverged.

"Failed migration found"

Error: P3009 migrate found failed migrations in the target database

A previous migration started but didn't finish. It got partway through — maybe it created one table but failed before creating the second. Now the database is in a half-changed state that no migration file describes.

What to try

Step 1: Check your migration status

Before you change anything, look at what the migration tool thinks is going on. Every tool has a status command:

# Prisma
npx prisma migrate status

# Django
python manage.py showmigrations

# Rails
rails db:migrate:status

# Alembic
alembic current
alembic history

This tells you which migrations have been applied, which are pending, and which have failed. Read the output carefully. It usually tells you exactly where things went wrong.

Step 2: Don't reset production

This is worth saying explicitly. If you're looking at a production database — one with real users and real data — do not run any "reset" command. Don't run prisma migrate reset. Don't drop and recreate the database. Don't run rails db:schema:load on a database that has data in it.

These commands destroy all data and rebuild from scratch. That's the correct move in development. In production, it's catastrophic.

Step 3: Fix the specific conflict

Most migration failures come down to one specific mismatch between what the migration expects and what the database actually looks like. If the migration says "add column X" and column X already exists, you have two options:

  1. Edit the migration to skip that step (remove the "add column" line if the column is already there)
  2. Mark the migration as applied without running it (if the database already matches what the migration would produce)

In Prisma, you can use prisma migrate resolve --applied "migration_name" to tell it "this migration is done, don't try to run it." In Django, you can use python manage.py migrate --fake app_name migration_name. In Alembic, you can stamp the revision with alembic stamp revision_id. Rails lets you manually insert the migration version into the schema_migrations table.

These commands are precise tools. They fix the specific disagreement between the database and the migration history. They don't touch your data.

Step 4: If a migration failed partway through

This is the trickiest situation. The migration started, made some changes, then hit an error and stopped. Now the database is halfway between two states.

You need to look at the migration file, figure out which steps completed before it failed, and either manually undo the partial changes or manually finish them. Then mark the migration as resolved.

This requires understanding what each line in the migration file does, and checking the database to see what actually happened. It's methodical work, not guesswork.

The nuclear option and when NOT to use it

Every migration tool has a "burn it down" command:

# Prisma
npx prisma migrate reset

# Django
python manage.py flush
# or dropping the database entirely

# Rails
rails db:reset

# Alembic
alembic downgrade base

These commands delete all data and rebuild the database from your migration files. Clean slate. All migration errors gone.

When this is fine: In development, when your database is full of test data that you don't care about. You're building and iterating and the migration history is a mess. Blow it away, start fresh, move on.

When this is catastrophic: In production. In staging with data you need. In any environment where the database contains information you can't recreate. Running a reset command on a production database doesn't "fix" the migration problem. It deletes your users' data. Their accounts, their orders, their uploads, their history — gone.

The fact that AI casually suggests this, with no warning about which environment you're in, is one of the most dangerous things about using AI for database problems.

The stakes

Migration errors feel like a technical nuisance — something between you and your deploy process. But the database behind those migrations contains real data. Every row is something someone created, bought, saved, or uploaded.

A wrong migration can rename a column and orphan every reference to it. It can drop a table that still has data in it. It can change a column type and silently truncate values. These aren't theoretical risks. They happen every day to people who trusted AI to "just fix the migration error."

The right approach is boring: read the status, understand the specific mismatch, fix that one thing, verify, move on. The wrong approach is fast: reset everything and hope for the best. The difference is whether your users' data survives.

Migrations tangled? Don't nuke the database.

Press the MeatButton and a real expert will look at your migration history, find the exact conflict, and fix it without touching your data. No resets, no guesswork. First one's free.

Get MeatButton