Known Migration Issues
This document catalogs the migration problems django-migraid detects and (where safe) auto-fixes.
E001 — Conflicting Leaf Migrations
Symptoms: CommandError: Conflicting migrations detected when running migrate or makemigrations.
Root cause: Two developers each created a migration from the same parent on separate branches. When the branches are combined, the migration DAG has two "leaf" (head) nodes in one app.
Manual fix: python manage.py makemigrations --merge
Auto-fix: python manage.py migraid fix-conflicts — linearizes the fork by renumbering the second leaf to follow the first.
E002 — Circular Migration Dependencies
Symptoms: CircularDependencyError during migrate or squashmigrations.
Root cause: App A depends on a migration in App B, and App B depends on a migration in App A.
Manual fix: Break the cycle by splitting one of the foreign keys into a separate migration, or restructuring cross-app dependencies.
Auto-fix: Not possible automatically — requires domain knowledge of which dependency to break.
W006 — Out-of-Order / Gap Numbering
Symptoms: Migration files numbered 0001, 0003 (skipping 0002), or two files with the same prefix after a rebase.
Root cause: After a git rebase, local migrations may have numbering that conflicts with or creates gaps relative to the target branch.
Manual fix: Rename migration files and update their dependencies lists.
Auto-fix: python manage.py migraid renumber <app>
E004 — Dependency on a Deleted Migration
Symptoms: NodeNotFoundError during migrate, or silent missing-migration errors.
Root cause: A migration's dependencies list references ("app", "name") where that migration file no longer exists on disk — typically from an incomplete squash transition or a cherry-pick that missed a file.
Manual fix: Update the dangling dependencies entry to point to the squash migration or the correct replacement.
Auto-fix: Not possible automatically — the correct replacement must be determined by the developer.
E005 — Renamed Applied Migration (Table Desync)
Symptoms: migrate reports a migration as unapplied even though its schema changes are already in the database, and an old migration name appears "missing." doctor shows a django_migrations row whose name has no file on disk while a same-suffix file exists.
Root cause: An applied migration was renamed on disk (e.g. rebase/renumber/fix-conflicts with --allow-applied, or a manual rename) without updating the django_migrations table, so the recorded name no longer matches the file.
Manual fix: UPDATE django_migrations SET name='<new>' WHERE app='<app>' AND name='<old>'; (the exact statement is in the doctor hint), or revert the file rename in git.
Auto-fix: Re-run the rename command with --update-db, which renames the file and the row together. Going forward, always pass --update-db instead of --allow-applied when renaming applied migrations.
W001 — Stale django_migrations Rows
Symptoms: django_migrations table contains rows for migrations that no longer exist on disk. Subsequent migrations may fail or produce confusing output.
Root cause: Migration files were deleted or renamed without updating the database, or a branch was switched without rolling back first.
Manual fix: Manually delete the orphaned rows from django_migrations.
Auto-fix: python manage.py migraid prune --yes (previews and confirms before deleting; add --dry-run to preview without any prompt).
W002 — RunPython / RunSQL Without Reverse Code
Symptoms: migrate --plan shows IRREVERSIBLE. Rolling back fails with an error.
Root cause: A RunPython operation was created without a reverse_code parameter (defaults to None). A RunSQL operation was created without reverse_sql.
Manual fix: Add reverse_code=migrations.RunPython.noop (to explicitly mark as irreversible) or a real reverse function. Add reverse_sql=migrations.RunSQL.noop for SQL operations.
Auto-fix: Not possible — requires domain knowledge of the correct reverse logic.
W003 — Incomplete Squash Transition
Symptoms: Both the squashed migration (with replaces=[...]) and the original migration files it replaces are still present on disk.
Root cause: Django's squashing process requires a manual transition step: after all environments have applied the squash migration, delete the originals and remove the replaces attribute. This is often forgotten.
Manual fix: Delete all migration files listed in the squash's replaces=[...] list, update any migrations that depended on them to depend on the squash migration instead, then remove the replaces attribute.
Auto-fix: Not currently automated — too risky without verifying all environments have applied the squash.
W004 — Merge Migrations in a Rebase-Workflow Repo
Symptoms: Merge migrations (created by makemigrations --merge) exist in a repository that uses git rebase rather than git merge. Execution order becomes non-deterministic.
Root cause: makemigrations --merge creates a migration that depends on two parents. In a rebase workflow this creates an inconsistency — the "merge" is semantically wrong.
Manual fix: Delete the merge migration and rebase/renumber the conflicting migrations instead.
Auto-fix: python manage.py migraid rebase
W005 — Non-Deterministic Dependency Ordering
Symptoms: Migration files show unnecessary diffs between developers or CI runs because dependencies list order varies.
Root cause: Django's makemigrations uses sets internally, and before Python 3.7+ hash-seed stabilization, dependency list order could vary between runs.
Manual fix: Alphabetically sort the dependencies list in affected migration files.
Auto-fix: Not currently automated. Use the doctor output to identify affected files.
I001 — Cross-App Dependency Risks
Symptoms: Migrations in multiple apps depend on each other in complex ways, making ordering fragile.
Root cause: ForeignKey relationships between apps create implicit migration dependencies that can become tangled during parallel development.
Manual fix: Review the dependency graph (migraid graph) and ensure cross-app dependencies are as minimal as possible.
I002 — --fake / --fake-initial Footgun Patterns
Symptoms: django_migrations table says a migration is applied but the actual database schema doesn't match.
Root cause: --fake was used to mark migrations as applied without actually running them. --fake-initial has similar risks on non-initial migrations.
Manual fix: Carefully diff the current schema against the expected schema, then apply missing DDL manually or re-run the affected migrations.