What this usually means
Django's migration graph is a directed acyclic graph. Each migration depends on exactly one previous migration (its parent). When two developers independently create migrations from the same parent (e.g., both branch from 0002, creating 0003_alice and 0003_bob), you end up with two leaf nodes. Django cannot decide which order to apply them—it sees a conflict. This is not a database state problem; it's a graph inconsistency. The fix is to merge the branches by creating a new migration that depends on both conflicting leaves, effectively resolving the graph.
The first ten minutes — establish facts before touching code.
- 1Run 'python manage.py showmigrations' to list all migrations and identify multiple leaf nodes (look for the same number prefix).
- 2Check 'python manage.py makemigrations --dry-run' — if it fails with conflict, you have a graph problem.
- 3Use 'python manage.py migrate --list' to see which migrations are applied vs unapplied.
- 4Inspect the migration files: compare the 'dependencies' attribute in each conflicting file.
- 5Run 'git log --oneline --graph' on the migrations directory to see the branching history.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchMigration files in 'yourapp/migrations/' — open each to check dependencies and numbering.
- searchGit log on the migrations directory: 'git log --oneline --graph yourapp/migrations/'
- searchDatabase table 'django_migrations' — query 'SELECT * FROM django_migrations ORDER BY applied;'
- searchPython manage.py showmigrations output — identify leaf nodes (migrations with no dependents).
- searchAny merge commits in git — see how migration conflicts were resolved.
- searchCI/CD pipeline logs — the exact error message and traceback from migrate command.
Practical causes, not theory. These are the things you will actually find.
- warningTwo developers ran 'makemigrations' on the same branch point without merging first.
- warningGit merge conflicts on migration files were resolved by keeping both files (duplicate numbering).
- warningSquash migrations incorrectly merged, creating a dependency cycle.
- warningManually editing migration dependencies to fix merge, introducing a cycle.
- warningRebasing branches that reorder migrations, causing dependency mismatch.
- warningDeploying a migration from a feature branch that conflicts with another deployed migration.
Concrete fix directions. Pick the one that matches your root cause.
- buildCreate a merge migration: 'python manage.py makemigrations --merge' — Django will prompt to create a new migration that depends on both conflicting leaves.
- buildManually create a migration file with dependencies listing both conflicting migrations, then run 'migrate'.
- buildIf the conflict is in the database state, use 'python manage.py migrate --fake <app> <migration_name>' to mark a migration as applied without running it.
- buildIn extreme cases, delete the problematic migration files, run 'python manage.py makemigrations' to recreate from scratch, then 'migrate --fake-initial' if needed.
- buildUse 'python manage.py squashmigrations' to flatten history and eliminate conflicts.
- buildPrevent future conflicts by enforcing a policy: always run 'makemigrations' on the latest merged master.
A fix you cannot prove is a guess. Close the loop.
- verifiedRun 'python manage.py showmigrations' — confirm only one leaf node per app.
- verifiedRun 'python manage.py makemigrations --check' — exit code 0 indicates no pending migrations.
- verifiedRun 'python manage.py migrate' — should complete without errors.
- verifiedQuery 'SELECT COUNT(*) FROM django_migrations WHERE app='yourapp';' and compare with number of migration files.
- verifiedRun 'python manage.py test' to ensure all tests pass after the fix.
- verifiedCheck that git status shows no untracked migration files (unless expected).
Things that make this bug worse or harder to find.
- warningDo not manually edit the 'dependencies' list in migration files unless you fully understand the graph.
- warningDo not delete migration files from the database without using '--fake' — you'll lose sync.
- warningDo not run 'migrate --fake' blindly — it can mask real conflicts.
- warningDo not ignore merge conflicts in migration files during git merge — resolve by creating a merge migration.
- warningDo not use 'makemigrations --merge' without reviewing the output — it may create circular deps if you have more than two leaves.
- warningDo not assume 'flush' or resetting the database is acceptable in production.
The Saturday Deployment That Failed Because of Two 0003s
Timeline
- 09:00Dev A runs 'makemigrations' on master (creates 0003_add_field_x). Pushes to master.
- 09:15Dev B pulls master, runs 'makemigrations' to add index (creates 0003_add_index). Push fails due to merge conflict.
- 09:20Dev B resolves merge by keeping both 0003 files. Pushes to master.
- 09:35CI pipeline runs 'migrate' for integration tests — fails with 'Conflicting migrations detected'.
- 09:40On-call engineer checks logs, sees multiple leaf nodes in 'showmigrations'.
- 09:50Runs 'makemigrations --merge' — creates 0004_merge, but the CI still fails because the merge migration wasn't applied.
- 10:00Engineer manually runs 'migrate' on staging, then updates CI to run migrations in order. Deployment succeeds.
- 10:15Postmortem: add git hook to prevent multiple leaves on master.
It was a Saturday morning, and I was sipping coffee when the alert came in: 'Deploy to production failed — migration conflict.' I jumped on the VPN and checked the CI logs. The error was clear: 'Conflicting migrations detected; multiple leaf nodes in migration graph for app 'users'.' I ran 'showmigrations' locally and saw two 0003 migrations: 0003_add_field_x and 0003_add_index. Both were unapplied.
I pulled the git history and saw that Dev A had pushed 0003_add_field_x to master. Dev B had pulled that, created his own migration (also numbered 0003), and when he pushed, Git forced a merge that kept both files. The migration graph now had two leaves—Django didn't know which one to apply first. Simple fix: run 'makemigrations --merge' to create a 0004 that depends on both. But the CI had already failed, and the merge migration wasn't in the repo yet.
I created the merge migration locally, committed it, pushed, and reran the pipeline. It passed. The lesson: never accept a git merge that duplicates migration numbers. We added a CI check that runs 'makemigrations --check' on every PR, and we now enforce that only one person can create migrations per sprint unless they coordinate.
Root cause
Two developers created migrations with the same parent (0002), resulting in two leaf nodes in the migration graph.
The fix
Created a merge migration that depends on both conflicting migrations using 'python manage.py makemigrations --merge'.
The lesson
Always run 'makemigrations' on the latest master to avoid duplicate numbering. Use CI checks to catch conflicts early.
Django migrations are stored as Python files in each app's 'migrations/' directory. Each file has a 'dependencies' attribute that lists its parent migrations. The graph must be a DAG (directed acyclic graph) with exactly one leaf per app (unless you have unapplied migrations).
A conflict arises when two migrations have the same dependency but different numbers (e.g., both 0003 depend on 0002). This creates two leaves. Django raises 'Conflicting migrations' because it cannot determine a linear order. The solution is to add a new migration that depends on both, merging the branches.
The '--merge' flag tells Django to generate a new migration that depends on all current leaf nodes. It interactively asks for a name. This is the fastest fix, but be careful: if you have more than two leaves (e.g., from multiple merges), the resulting migration may have many dependencies, which is fine but messy.
After generating the merge migration, run 'python manage.py migrate' to apply it. Then verify with 'showmigrations' that there's only one leaf. If you prefer manual control, you can create the merge migration file by hand: copy an existing migration, set dependencies to a list of the conflicting migration names, and set operations to an empty list.
Sometimes, manual editing of dependencies introduces a cycle. For example, migration A depends on B, and B depends on A. This causes 'RecursionError' during migration planning. To fix, identify the cycle by inspecting dependencies. You may need to break the cycle by making one migration depend on a common ancestor instead.
Use 'python manage.py showmigrations --plan' to see the proposed order—it will fail with a cycle error. The only way out is to modify one of the migration files to remove the circular dependency. If the cycle was introduced by a merge, consider recreating the migrations from the last working state.
The best fix is prevention. Enforce that developers always pull the latest master before running 'makemigrations'. In CI, add a step: 'python manage.py makemigrations --check' (which exits non-zero if there are unapplied migrations). This catches uncommitted migrations before merge.
Another approach: use a single 'migrations coordinator' per sprint who creates all migrations. Or use a tool like 'django-migration-check' that alerts on duplicate numbers. Git hooks can also refuse pushes that introduce multiple leaves.
Sometimes the migration graph is fine but the database 'django_migrations' table is out of sync (e.g., a migration was applied manually or rolled back incorrectly). This manifests as 'InconsistentMigrationHistory'. The fix is to use '--fake' to mark migrations as applied or unapplied to match the database.
Query the table: 'SELECT * FROM django_migrations WHERE app='your_app' ORDER BY applied;'. Compare with the file list. If a migration is in the table but not on disk, you may need to run 'migrate --fake' to remove it. If a migration is on disk but not in the table, run 'migrate <app> <migration_name> --fake'.
Frequently asked questions
What does 'Conflicting migrations detected; multiple leaf nodes' mean?
It means the migration graph for an app has more than one migration that no other migration depends on (leaves). This usually happens when two developers created migrations from the same parent. Django can't determine which order to apply them because they are parallel branches. The fix is to create a merge migration that depends on both leaves.
Can I just delete one of the conflicting migration files?
Not directly. If you delete a migration file that hasn't been applied to the database, it's safe to remove it and run 'makemigrations' again. But if it's already applied, you must first 'migrate' to a previous state or use '--fake' to mark it as unapplied. Deleting an applied migration file without updating the database will cause 'InconsistentMigrationHistory'.
How do I prevent migration conflicts in my team?
Enforce that developers run 'makemigrations' only on the latest master. Use a pre-commit hook that runs 'makemigrations --check'. Alternatively, designate one person per sprint to create all migrations. In CI, add a step that fails if there are multiple leaves or unapplied migrations.
What is the difference between 'migrate --fake' and 'makemigrations --merge'?
'--fake' marks a migration as applied or unapplied in the database without actually running its operations. It's used to sync the database state with the migration files. '--merge' creates a new migration that depends on conflicting leaves, resolving the graph conflict. They solve different problems: '--fake' fixes database sync, '--merge' fixes graph structure.
Can circular dependencies happen with migrations?
Yes, if you manually edit the 'dependencies' attribute of a migration to point to a migration that depends on it, you create a cycle. This causes a 'RecursionError' during migration planning. The fix is to break the cycle by making one of the migrations depend on a common ancestor instead. If the cycle was introduced by a merge, consider recreating the migrations from a clean state.