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.
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 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.
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.