Fixing Alembic Multiple Heads After a Branch Merge
Two feature branches merged into main cleanly — Git reported no conflict because each branch added a new file under alembic/versions/ and new files never collide. Then the deploy step ran alembic upgrade head and stopped dead with Multiple head revisions are present for given argument ‘head’. Nothing is broken in your schema and nothing is wrong with either revision individually; the problem is structural. Each branch generated its revision on top of the same parent, so the revision graph that is supposed to be a straight line now forks into two tips, and alembic upgrade head has no single target to walk toward. This page is the fix: confirm the fork with alembic heads, rejoin the two tips with a merge revision, apply it, and adopt the review-time habit that stops the fork forming on the next pair of parallel branches. The graph mechanics behind it are covered in depth in the parent Alembic & SQLAlchemy Migrations guide.
The failure is deploy-blocking but completely recoverable, and it never touches your data. A forked history is still a valid directed acyclic graph — Alembic simply refuses to guess which tip you meant. It is one of the most common things that pages a SQLAlchemy team the moment parallel work lands, and it fits squarely inside the broader ORM & Framework Migration Workflows discipline of keeping a linear, reviewable migration history.
Symptom / Error Signatures
The primary signature comes from alembic upgrade head itself, and the exact wording is worth matching because it tells you Alembic knows about more than one tip:
FAILED: Multiple head revisions are present for given argument 'head'; please
specify a specific target revision, '@head', to narrow to a specific
head, or 'heads' for all heads
Confirm it directly with alembic heads, which prints one line per tip. A healthy project prints exactly one; a forked one prints two (or more), each flagged (head):
7f3a9c2b1d84 (head)
b2e5d1a8c907 (head)
alembic history shows the shape — both revisions naming the same down_revision, the graph splitting at a shared parent and never rejoining:
9a0c... -> 7f3a9c2b1d84 (head), add orders.discount_code
9a0c... -> b2e5d1a8c907 (head), add users.last_login_at
c81f... -> 9a0c..., create orders table
You may also see the same error surface from alembic revision --autogenerate (it cannot pick a down_revision for the new file) or, if both branches were already deployed to a shared database, two rows in the alembic_version table where there is normally one. Any of these confirms you are on the right page: the history forked, and the fix is a merge revision, not an edit to either existing file.
Root Cause Analysis
Alembic encodes order in pointers, not filenames. Every revision file names a down_revision — the revision it builds on — and alembic upgrade head walks that chain backward from the requested tip to the current alembic_version, then replays the upgrade() functions forward. For this walk to be unambiguous the graph must resolve to exactly one tip. When engineer A branches off revision 9a0c and runs autogenerate, her file sets down_revision = "9a0c...". When engineer B branches off the same 9a0c independently and does the same, his file also sets down_revision = "9a0c...". Git merges both files without conflict because they have different filenames and different content — there is no text to collide. But now two revisions claim 9a0c as their parent, so 9a0c has two children with no successor linking them, and the graph has two heads. Alembic detects the ambiguity and refuses rather than silently applying them in an arbitrary order that might not be the order you tested.
There are two legitimate ways out, and they diverge in what history they leave behind. A merge revision (alembic merge) creates a new, empty revision whose down_revision is a tuple naming both tips; applying it collapses the two heads into one shared descendant, preserving both branches’ revisions exactly as written and as already applied on any database. Manual linearization instead re-points one revision’s down_revision at the other, turning the fork into a straight line as if the branches had always been sequential. The table below is the decision surface:
| Approach | What it does | History shape | When it is safe | Cost |
|---|---|---|---|---|
alembic merge heads |
Adds an empty revision with both tips as parents | Fork that rejoins (diamond) | Always — including when both branches already deployed | One extra revision file forever |
Manual re-point of down_revision |
Rewrites one file to sit after the other | Straight line, no merge node | Only if neither branch’s revision has been applied to any database yet | Rewrites applied history; breaks alembic_version on any DB that already ran the moved revision |
The rule that follows: if either revision has already been applied anywhere — a teammate’s database, staging, a preview environment — you must use alembic merge, because re-pointing a revision that a database has already recorded in alembic_version will make Alembic unable to locate it. Merge is the safe default; linearization is a cleanup you only reach for on revisions that have never left your workstation.
9a0c as their down_revision, so the graph forks into two heads. alembic merge adds a revision whose down_revision tuple names both tips, collapsing the diamond back to one head.Immediate Mitigation
The following restores a working alembic upgrade head in one revision. Run it on a workstation or CI checkout that has both branches’ revision files present under alembic/versions/.
1. Confirm exactly how many heads exist and which they are. Never merge blind — read the identifiers so the merge names the right tips.
# Context: read-only; lists every current tip of the revision graph. No database writes.
alembic heads --verbose
Verify before proceeding: two (or more) revisions print, each flagged (head). Note both identifiers.
2. Inspect the divergence so you understand what each branch added. This confirms the two tips share a parent and that neither depends on the other.
# Context: read-only; prints the graph so you can see the shared down_revision. No writes.
alembic history --verbose | head -n 40
Verify before proceeding: both heads name the same down_revision, confirming a clean fork rather than a deeper tangle.
3. Create the merge revision that rejoins the tips. The literal heads argument tells Alembic to name every current tip as a parent; pass explicit identifiers instead if you want only a specific pair merged.
# Context: workstation/CI checkout; writes a new empty revision file under alembic/versions/.
# Applies NOTHING to any database — it only edits the graph. Commit this file with the branch.
alembic merge -m "merge discount_code and last_login_at branches" heads
Verify before proceeding: a new file appears in alembic/versions/, its down_revision is a tuple of both head identifiers, and its upgrade()/downgrade() bodies are empty (pass). Re-run alembic heads — it must now print exactly one head, the merge revision.
4. Apply forward-only, as a discrete deploy step. Because the merge revision is empty it issues no DDL, so it is safe to run at any time; the real schema changes were already in the two branch revisions.
# Context: run as the migration role, BEFORE the app fleet rolls, exactly like any deploy.
# The merge revision itself is empty DDL, but this also applies any branch revision not yet run.
alembic upgrade head
Verify before proceeding: the command exits zero and alembic current reports the single merge revision. If both branches had already been applied to this database (two rows in alembic_version), Alembic collapses them to one row pointing at the merge revision as part of this upgrade.
alembic_version down to a single row.Permanent Fix / Long-Term Pattern
Merging is the cure; the prevention is keeping the graph linear so a second head never forms. The root enabler is that Git cannot see a semantic conflict between two new files, so the guard has to live above Git. The durable pattern has two halves: a rebase-before-merge habit and a CI gate that fails a pull request carrying more than one head.
The habit is to treat the migration head like a fast-moving shared resource. Before merging a feature branch, pull the latest main, and if a new revision landed there since you branched, re-point your revision’s down_revision at the new head while your revision is still unapplied anywhere — the safe form of linearization from the table above. This keeps history a straight line and avoids ever creating a merge node in the first place. When two branches genuinely developed in parallel and both are already applied on shared environments, skip the rebase and merge instead; both are legitimate, and the deciding factor is always whether the revision has been recorded in any alembic_version yet.
The gate makes the invariant non-negotiable. A one-line check in CI counts the heads and fails the build if there is more than one, so a fork is caught at pull-request time instead of at deploy time:
# Context: CI step on every pull request; read-only against the repo's migration graph.
# Fails the build the moment the history forks, before the merge ever reaches a deploy.
test "$(alembic heads | wc -l)" -eq 1 || { echo "FORKED: multiple Alembic heads"; exit 1; }
This is the same single-head invariant the parent Alembic & SQLAlchemy Migrations guide builds its whole revision discipline on, and it pairs naturally with the forward-only, additive sequencing described in running Alembic migrations with zero downtime: a linear history is what lets each deploy apply exactly one predictable step. Where autogenerate is involved, the same review rigor that catches an autogenerated revision missing changes is where a reviewer should also confirm the new revision’s down_revision points at the current head and not a stale one.
Verification Checklist
Up one level: Alembic & SQLAlchemy Migrations.
Related
- Alembic & SQLAlchemy Migrations
- Why Alembic Autogenerate Misses Changes
- Running Alembic Migrations with Zero Downtime
Frequently Asked Questions
Why did Git merge cleanly but Alembic still fork?
Git compares files, and each branch added a different new file under alembic/versions/, so there was no text to conflict on. Alembic compares pointers: both new files set the same down_revision, so the revision graph gained two children of one parent with nothing linking them — two heads. The conflict is semantic, in the graph, not textual in the files, which is exactly why it slips past Git and only surfaces when alembic upgrade head needs a single target.
Should I merge the heads or just edit one revision’s down_revision?
Merge with alembic merge heads unless neither revision has been applied to any database yet. Re-pointing a down_revision rewrites history into a straight line, which is clean but breaks alembic_version on any database that already recorded the revision you moved — Alembic will report it cannot locate that revision. If both branches only ever ran on your workstation, linearizing is fine; the instant either has touched a shared environment, use a merge revision, which is always safe because it adds a node rather than moving one.
Is a merge revision empty, and is it safe to deploy?
Yes and yes. alembic merge writes a revision whose only job is to name both former heads as parents; its upgrade() and downgrade() bodies are pass, so it issues no DDL and acquires no locks. Applying it is a no-op against the schema that simply collapses the two tips into one head, and if both branch revisions were already applied it also reconciles the two alembic_version rows down to one. You can safely run it as an ordinary forward-only deploy step at any time.