Resolving Migration Version Conflicts During Merges

Two engineers branched off main on the same morning. Both added a migration. Both named it V14__. Now their branches merge into the same release, and the deploy refuses to run — or worse, runs one migration, skips the other, and leaves production in a state no checksum recognizes. This is the most routine conflict in schema version control and one of the few that a git merge cannot resolve for you, because the conflict is not in the file contents but in the ordering of a linear chain that two branches each extended independently. The alert that lands engineers here is a failed deploy with a duplicate version, a checksum mismatch, or — on Alembic — a Multiple head revisions are present error. This page covers detecting the collision, renumbering or rebasing the chain, and resolving an Alembic multiple-heads split safely.

Symptom / Error Signatures

Migration ordering conflicts surface at merge time or at the first deploy after merge:

  • Flyway refuses to run with a duplicate version: FlywayException: Found more than one migration with version 14.
  • Flyway detects that a new migration was inserted before one already applied: Detected resolved migration not applied to database: 14. To ignore this migration set -ignoreMigrationPatterns.
  • A checksum mismatch on a previously applied version: Migration checksum mismatch for migration version 14 because two files share the version.
  • Alembic reports a branched history: Multiple head revisions are present for given argument 'head'; please specify a specific target revision.
  • Rails or Django apply migrations out of timestamp order, leaving a schema-version table that records the migrations in an order the files no longer imply.

The branching hygiene that prevents most of these — short-lived branches, rebasing before merge — is covered in Git Branching Strategies for Schema Version Control; this page is the recovery procedure for when a collision has already happened.

Root Cause Analysis

A migration history is a linear, append-only sequence: each migration assumes every prior one has already run. Version control, by contrast, is a branching model where two people can independently append to what they each believe is the tip. When their branches merge, both appended at the same point, and the chain now has two “next” migrations claiming the same slot. The tool sees either a duplicate identifier (Flyway’s V14) or a forked graph (Alembic’s two heads), and it cannot guess which should run first — that ordering carries real meaning, because if migration A adds a column that migration B indexes, running B first fails.

The resolution depends on how the tool identifies migrations. Sequence-numbered tools (Flyway V14, Rails/Django by timestamp) collide on the number and are fixed by renumbering the later migration so the chain is linear again. Graph-based tools (Alembic, where each revision names its down_revision parent) do not collide on a number — they fork into two heads — and are fixed by creating an explicit merge revision that names both heads as parents, re-linearizing the graph. The danger in both cases is renumbering a migration that has already been applied somewhere: change the identity of an applied migration and every environment that ran it now has a checksum or version that no longer matches the repository.

Aspect Sequence-numbered (Flyway, Rails, Django) Graph-based (Alembic)
Migration identity version number / timestamp revision id + down_revision parent
Conflict shape duplicate version number multiple heads
Fix renumber the later migration alembic merge creates a merge revision
Safe only if the renumbered migration is unapplied both heads are unapplied or the merge is forward-only
After fix, verify chain is strictly increasing alembic heads shows a single head

Whether your tool is sequence-numbered or graph-based is itself a property worth knowing when choosing one, as discussed in Flyway vs Liquibase: Choosing the Right Migration Tool.

Re-linearizing a forked migration chain The top row shows two branches both adding V14, resolved by renumbering one to V15 so the chain is linear. The bottom row shows two Alembic heads joined by a merge revision into a single head. Two Appends, One Chain Sequence-numbered: renumber V13 V14 (A) V14 (B) V14 (A) V15 (B') linear again Graph-based: merge revision head A head B merge rev single head
Sequence-numbered tools re-linearize by renumbering the later migration; graph-based tools join the two heads with an explicit merge revision.

Immediate Mitigation

Resolve the collision before merging to the release branch — never deploy a forked chain.

  1. Confirm which migrations are unapplied in every shared environment. You may only renumber a migration that has not run anywhere.
-- PostgreSQL · read-only · run against each shared environment (staging, prod)
-- If a colliding version already appears here as applied, you must NOT renumber it.
SELECT version, success, installed_on
FROM flyway_schema_history WHERE version IN ('14') ORDER BY installed_on;
  1. For a sequence-numbered conflict, renumber the later migration so the chain is strictly increasing. Pick by the intended apply order — if B depends on A, A stays V14 and B becomes V15.
# Shell · run in your feature branch before merging · file rename only, no DB change
# Renumber the second migration so the version sequence is linear again.
git mv migrations/V14__add_orders_index.sql migrations/V15__add_orders_index.sql
  1. For an Alembic multiple-heads conflict, create a merge revision that names both heads as parents. This re-linearizes the graph without re-running either migration.
# Shell · run in the feature branch · generates a merge revision file, applies nothing yet
# The merge revision declares both heads as down_revisions, collapsing them to one head.
alembic merge -m "merge feature-a and feature-b heads" head_a_rev head_b_rev
  1. Re-verify the chain is single-headed and ordered before you push the merge. A passing check here is the gate to deploy.
# Shell · read-only inspection · must print exactly one revision
alembic heads          # expect a single head
alembic history        # confirm the linear order is what you intend

Permanent Fix / Long-Term Pattern

The collision is cheap to prevent and expensive to clean up, so push the fix upstream of the merge. The practices that make ordering conflicts rare are the same disciplined branch habits described in Schema Version Control Basics and Git Branching Strategies for Schema Version Control.

Rebase the migration chain before merge, not after. Keep feature branches short-lived and rebase onto the latest main before opening the merge. Rebasing surfaces the collision while the migration is still unapplied everywhere, when renumbering is free. The cardinal rule: never renumber or rewrite a migration that has already been applied to any shared environment — doing so breaks the checksum on every database that ran it. If an applied migration must change, append a new corrective migration instead.

Use a merge-queue or required check that rejects a forked chain. A continuous integration step that runs the tool’s own validation (flyway validate, alembic heads | wc -l) as a required gate refuses to merge a branch that would create a duplicate version or a second head. This turns a deploy-time failure into a pull-request failure.

# Shell · CI required check · read-only validation, fails the build on a forked chain
# For Alembic: fail if more than one head exists after merging the branch.
test "$(alembic heads | grep -c '(head)')" -eq 1 || { echo "Forked migration chain"; exit 1; }

Prefer monotonic, collision-resistant identifiers. Where the tool allows it, timestamp-based or content-hash identifiers collide far less often than hand-incremented integers. Two branches almost never generate the identical timestamp, so the worst case degrades to an ordering review rather than a hard duplicate.

Verification Checklist

  • flyway validate passes).
  • alembic heads reports exactly one head after the merge revision is added.

Frequently Asked Questions

Can I just renumber the conflicting migration and deploy? Only if that migration has not been applied to any shared environment yet. Renumbering changes the migration’s identity, so any database that already ran it under the old number will report a checksum or version mismatch on the next deploy. Confirm it is unapplied everywhere first; if it is already live somewhere, append a corrective migration instead of editing it.

What does Alembic’s “Multiple head revisions are present” actually mean? Two branches each created a revision whose down_revision points at the same parent, so the revision graph forked into two tips. Alembic refuses to pick one. Run alembic merge head_a head_b to create a merge revision that declares both as parents, collapsing the graph back to a single head that upgrades cleanly.

How do I stop these conflicts from recurring? Keep branches short-lived, rebase onto the latest main before merging so collisions surface while migrations are still unapplied, and add a required CI check that fails on a duplicate version or a second head. Where the tool supports it, use timestamp or hash identifiers so two branches rarely collide on the same number.