Cheatsheet for handling Alembic branches.

How branches form

Two devs branch off the same parent rev. After both merge to main: two heads.

abc123
├── def456  (Alice)
└── ghi789  (Bob)

Detect

alembic heads
# Multiple lines = branched

Merge

alembic merge -m "merge X+Y" def456 ghi789

Creates a merge revision with two parents.

abc123
├── def456 ─┐
│           ├── merge (j1k2l3)
└── ghi789 ─┘

Merge migration content

Usually empty (just a graph fix):

def upgrade(): pass
def downgrade(): pass

If the two branches conflict (e.g., both renamed the same column differently): reconcile manually inside the merge.

CI gate (single head)

- name: Check single head
  run: |
    if [ "$(alembic heads | wc -l)" -gt 1 ]; then
      echo "Multiple heads — merge required"
      exit 1
    fi

Block PRs that introduce a branch without resolution.

Avoid branches in workflow

  • Pull main before generating.
  • Quick review + merge.
  • Communicate when adding migrations.

For small teams: branches happen rarely if discipline holds.

Rebase before merge (alternative)

# Locally, before pushing
git pull --rebase main
alembic upgrade head                    # apply latest
alembic revision --autogenerate -m "..."  # generates with correct parent

Cleaner history than merge migrations.

Branch labels (for monorepo)

# Per-app branch labels
revision = "api_001"
down_revision = None
branch_labels = ("api",)

revision = "admin_001"
down_revision = None
branch_labels = ("admin",)

Then:

alembic upgrade api@head           # only api branch
alembic upgrade admin@head

Merge multiple branches

alembic merge -m "merge all" a1 b1 c1

Three parents. Rare.

Recovery from “what just happened”

alembic heads
alembic history --verbose

Map the graph. Identify the problem revision. Use alembic stamp <rev> to set state if needed.

Down revision tuple

For merge revisions, down_revision is a tuple:

revision = "j1k2l3"
down_revision = ("def456", "ghi789")

Manual graph fix (last resort)

Edit a revision’s down_revision and revision. Dangerous; only for unshared migrations.

For already-applied migrations: don’t rewrite history. Add a new migration.

When branches are intentional

Multi-app monorepo with independent migration trees:

api branch: api_001 → api_002 → ...
admin branch: admin_001 → admin_002 → ...

Set branch_labels per revision. alembic upgrade api@head per branch.

Common mistakes

  • Generating a migration with stale local main → branch.
  • Force-pushing migration history — breaks deployments that ran the old.
  • Editing down_revision after the migration is shared.
  • Merging without understanding what each branch did.

Read this next

If you want my single-head CI gate + branch-resolution playbook, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .