Introduction

Django migration conflicts occur when two developers create migrations on different branches that modify the same model, and both migrations end up with the same numeric prefix or conflicting dependencies. After merging branches, Django detects multiple leaf migrations for an app and refuses to run migrations until the conflict is resolved. This is one of the most common friction points in team Django development, especially in projects with multiple concurrent feature branches touching the same models.

Symptoms

Running python manage.py migrate after a merge:

bash
CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0015_add_email_field, 0015_add_phone_number in users on default).
To fix them run 'python manage.py makemigrations --merge'

Or during CI deployment:

bash
django.db.migrations.exceptions.InconsistentMigrationHistory:
Migration admin.0001_initial is applied before its dependency users.0015_add_email_field

Or when running makemigrations:

bash
You are trying to add a new field to 'userprofile' but the migration
history has diverged. Multiple leaf nodes detected.

Common Causes

  • Parallel development on same model: Two branches both add a field to UserProfile, creating 0015_add_email.py and 0015_add_phone.py
  • Migration squash on one branch: One branch squashes migrations while another branch adds a new migration depending on the pre-squash state
  • Reordering migrations manually: Editing migration files to change the dependencies list creates graph inconsistency
  • Deleted migration files: Removing migration files from version control but they were already applied in production
  • Third-party app migration changes: Upgrading a package that changes its migration files, conflicting with local state

Step-by-Step Fix

Step 1: Detect the conflict

```bash # Check for conflicting migrations python manage.py makemigrations --check --dry-run

# See the current migration state python manage.py showmigrations ```

Look for apps with multiple migrations marked with [X] at the same position:

bash
users
 [X] 0013_auto_20240101_1200
 [X] 0014_userprofile_bio
 [X] 0015_add_email_field
 [X] 0015_add_phone_number   <-- conflict: two 0015 migrations

Step 2: Create a merge migration

bash
python manage.py makemigrations --merge

This generates a migration that depends on both conflicting migrations:

```python # users/migrations/0016_merge.py from django.db import migrations

class Migration(migrations.Migration): dependencies = [ ("users", "0015_add_email_field"), ("users", "0015_add_phone_number"), ]

operations = [] ```

Step 3: Handle complex dependency conflicts

If the two migrations modify the same field, a simple merge is not enough. You need to decide which version wins:

```bash # Roll back to the common ancestor python manage.py migrate users 0014

# Delete the conflicting migration files rm users/migrations/0015_add_*.py

# Create a single combined migration python manage.py makemigrations users

# Verify the result python manage.py migrate --plan ```

The plan output shows the exact execution order:

bash
Planned operations:
  users.0015_add_combined_fields
    Alter field email on userprofile
    Alter field phone on userprofile
    Add field avatar to userprofile

Step 4: Fix inconsistent migration history in production

If production has applied migrations that differ from the merged codebase:

```bash # Check what migrations are actually applied python manage.py showmigrations

# Fake the problematic migration if the schema is already correct python manage.py migrate users 0015_add_email_field --fake

# Then apply the merge python manage.py migrate users 0016_merge

# Finally apply remaining migrations python manage.py migrate ```

Prevention

  • Run python manage.py makemigrations --check in CI to catch migration conflicts before merge
  • Rebase feature branches onto main before merging to surface migration conflicts early
  • Use descriptive migration names like 0015_userprofile_add_email_field.py instead of relying on auto-generated numbers
  • Document migration dependencies when working on shared models in team standups
  • Consider using --squash periodically to reduce migration count and simplify the graph
  • Add a pre-commit hook that runs makemigrations --check --dry-run to detect unstated model changes