Fixing Django InconsistentMigrationHistory Errors

python manage.py migrate stops before doing anything: django.db.migrations.exceptions.InconsistentMigrationHistory: Migration orders.0031_order_region is applied before its dependency customers.0012_customer_tier on database 'default'. The deploy is blocked, and the tempting fix from a search result is to fake your way past it. Sometimes that is right. Sometimes it records as applied a migration whose schema changes never happened, and the next deploy fails in a far more confusing way — or silently runs code against a column that does not exist. This guide explains what Django is checking, how to find out which of the two states — history or schema — is wrong, and how to repair the history precisely. It belongs to the operational side of Django Migrations Without Downtime.

What Django Compares Before Migrating Django loads the migration graph from files, where orders.0031 depends on customers.0012. It reads django_migrations from the database, which shows orders.0031 applied but customers.0012 not applied. Because an applied migration's dependency is unapplied, the graph is inconsistent and migrate refuses to run. What Django Compares Before Migrating Migration files orders.0031 depends on customers.0012 django_migrations 0031 applied · 0012 missing Consistency check applied node with unapplied parent Actual schema the part you must inspect
The error is about the history table contradicting the dependency graph — it says nothing yet about whether the actual schema is right.

Symptom / Error Signatures

The error is raised by migrate, makemigrations (which checks consistency against the default database) and some test runners:

django.db.migrations.exceptions.InconsistentMigrationHistory:
Migration orders.0031_order_region is applied before its dependency customers.0012_customer_tier on database 'default'.

A classic variant appears when a custom user model is introduced after the project already ran migrations: Migration admin.0001_initial is applied before its dependency accounts.0001_initial. Related symptoms of a history-versus-schema mismatch are relation "..." already exists when Django tries to create something the database already has, and column "..." does not exist when history claims a migration ran that did not.

Root Cause Analysis

Django’s migrate command builds a directed graph from migration files, reads applied migrations from the django_migrations table, and checks that every applied migration’s dependencies are also applied. The error means history and graph disagree. The usual causes:

Cause How it happens Which side is wrong
Dependency added after the fact a migration was edited to depend on a newer migration from another app the graph changed; history is fine
Branches merged in different orders environments applied migrations from two branches in different sequences history order, usually harmless
Manual --fake in the past someone faked a migration without its dependencies history
Custom user model introduced late admin and auth migrations ran before the new user app existed history, and often schema
Restored database from an older backup django_migrations rolled back but code did not both need checking

The error only protects the history. The decision that matters is whether the schema actually contains what each migration in question creates. Faking marks a migration as applied without running it, so it is correct only when the schema already reflects that migration.

Fake It or Run It? Decision tree for the unapplied dependency. Inspect the database for the objects the dependency creates. If they all exist, fake the dependency. If they do not, the dependency must run; if it can run cleanly now, run it with a targeted migrate, otherwise write and review a repair migration. Fake It or Run It? Do the dependency's objects exist in the DB? yes no migrate --fake that dependency Can it run cleanly now? yes no Run it with a targeted migrate Write a repair migration; review
--fake is only correct when the schema already contains the migration's effects — check the database first, every time.

Immediate Mitigation

1. Read both sides. List the migrations Django believes are applied and the plan it would follow.

# Shell · deploy job or workstation with production-read settings · reads django_migrations, changes nothing
python manage.py showmigrations customers orders
-- PostgreSQL · read-only
SELECT app, name, applied FROM django_migrations
WHERE app IN ('customers', 'orders') ORDER BY applied;

2. Inspect what the missing dependency would create. Print its SQL and check the database for each object.

# Shell · prints the SQL for the unapplied dependency without running it
python manage.py sqlmigrate customers 0012
-- PostgreSQL · read-only · does customers.0012's column exist?
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'customers_customer' AND column_name = 'tier';

3a. If the schema already has it, fake only that migration. This inserts one row into django_migrations and runs no SQL.

# Shell · migration settings · WARNING: records customers.0012 as applied without running it
python manage.py migrate customers 0012 --fake
python manage.py migrate --plan   # should now proceed without the error

3b. If the schema does not have it, run it. When the dependency is independent of the already-applied later migration — typical when two apps’ migrations were simply applied in a different order — you can apply it directly with its SQL, then record it. Review the SQL for locks first, per DDL Lock Management & Timeouts.

# Shell · migration settings · run the dependency's SQL, then record it
# WARNING: only when customers.0012 does not depend on anything unapplied and its DDL is safe online.
python manage.py sqlmigrate customers 0012 | psql "$DATABASE_URL" -v ON_ERROR_STOP=1
python manage.py migrate customers 0012 --fake

4. Handle the custom-user-model variant separately. When admin.0001_initial is reported as applied before accounts.0001_initial, the project switched to a custom user model after the initial migrations ran. The tables for the new user model may or may not exist, and foreign keys from admin, auth and your own apps still point at auth_user. There is no safe one-line fix: either create the new user table and fake accounts.0001_initial once its structure matches, or — for young projects — rebuild the database from migrations. On an established production database, treat it as a planned data migration, moving users and repointing foreign keys in stages, rather than an error to suppress.

Whatever the variant, record what you did. Save the before-and-after showmigrations output and the SQL you ran in the incident or deploy notes; the next person to see an unusual row in django_migrations will need to know why it is there.

Permanent Fix / Long-Term Pattern

Prevent the conditions that create inconsistency. Never edit the dependencies of a migration that has been applied anywhere shared; add a new migration instead. Resolve parallel branches with makemigrations --merge, which creates an explicit merge migration rather than relying on order — the same principle as resolving migration version conflicts during merges. Treat --fake as a privileged operation that requires evidence: a note in the deploy log of which objects were checked and found present.

Add two automated checks. python manage.py migrate --plan in CI against a copy of production’s django_migrations table catches inconsistency before deploy day. And a periodic drift check comparing a database built from migrations with production — as in detecting production schema drift against a desired state — catches the more dangerous case where history and schema disagree without Django noticing. When squashing, keep the replaced migrations until every environment has applied the squashed one, per squashing migration history safely.

Guarding Migration History in CI Pipeline. A snapshot of production's django_migrations is loaded into CI; migrate --plan runs against it and fails on inconsistency; a schema drift check compares a migrations-built database with production; then migrate runs in the deploy. Guarding Migration History in CI Load history prod django_migrations plan consistent? Drift check migrations vs prod schema drift schema matches? Deploy migrate once, lock_timeout fix graph in PR reconcile first fail
Replaying production's history table in CI moves this error from deploy day to the pull request.

Verification Checklist

Frequently Asked Questions

Is it safe to run migrate --fake to get past the error? Only for migrations whose effects already exist in the database. Faking records the migration as applied without running any SQL, so faking a migration whose changes are missing leaves the schema behind while Django believes it is current.

What does --fake-initial do? It fakes an app’s initial migration only if all tables it creates already exist. It is meant for adopting migrations on an existing database and does not check columns added by later migrations.

Why does makemigrations also raise this error? It checks history consistency against the default database connection before generating migrations. If the development database has inconsistent history, fix that database or point makemigrations at a clean one.

Can I delete rows from django_migrations? Yes, deleting a row marks that migration as unapplied, and Django will try to run it next time. Do it only when you are certain the migration’s changes are absent, or when you intend to rerun an idempotent migration.