sqlalchemy/alembic · error · RangeNotAncestorError

Revision is not an ancestor of revision

Error message

Revision %s is not an ancestor of revision %s

What it means

Raised as RangeNotAncestorError during _collect_downgrade_revisions when the computed downgrade_revisions set is empty and the target revision is not among the current heads. This means the revision you are trying to downgrade away from is not actually present in the active migration path, so there is literally nothing to drop. The message 'Revision %s is not an ancestor of revision %s' comes from the RangeNotAncestorError class constructor.

Solutions

  1. Run 'alembic current' to see what revision the database is actually at.
  2. Run 'alembic history' to confirm the target revision exists in the active lineage.
  3. If the target is on a different branch, switch to the correct branch or merge branches first.
  4. If already at/below the target, no downgrade is needed — the database state is already correct.

Example fix

# before
alembic downgrade abc123  # abc123 not in current lineage

# after
alembic current             # check actual DB state
alembic history --verbose    # confirm lineage
alembic downgrade <correct_ancestor_rev>
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_downgrade_target(script_dir, target_revision):
    rev_map = script_dir.revision_map
    target = rev_map.get_revision(target_revision)
    if target is None:
        return False
    heads = rev_map.get_revisions(rev_map.heads)
    # check that target is an ancestor of at least one current head
    for head in heads:
        ancestors = {
            r.revision for r in rev_map._get_ancestor_nodes([head])
        }
        if target.revision in ancestors or target.revision == head.revision:
            return True
    return False

Try / catch

from alembic.script.revision import RangeNotAncestorError

try:
    command.downgrade(config, target)
except RangeNotAncestorError as e:
    print(f"Target {e.upper} not in current lineage. Run 'alembic history' to check.")
    raise

Prevention

When it happens

Trigger: Calling alembic downgrade with a target revision that is not in the current heads' ancestry (revision.py:1408-1415: target_revision is not None, downgrade_revisions is empty, and target_revision not in heads). Common when passing a revision id from a different branch or a revision that was already removed.

Common situations: Downgrading toward a revision that lives on a branch not currently applied. Specifying a revision id from an old migration file that was deleted. Database is already at or below the target revision. Mixing revision identifiers from different projects/environments.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/63469407ef96bdb4. Report an issue: GitHub.

Appendix: source

Thrown at alembic/script/revision.py:1415

        )

        # Emit revisions to drop in reverse topological sorted order.
        downgrade_revisions.intersection_update(active_revisions)

        if implicit_base:
            # Wind other branches back to base.
            downgrade_revisions.update(
                active_revisions.difference(self._get_ancestor_nodes(roots))
            )

        if (
            target_revision is not None
            and not downgrade_revisions
            and target_revision not in heads
        ):
            # Empty intersection: target revs are not present.

            raise RangeNotAncestorError("Nothing to drop", upper)

        return downgrade_revisions, heads

    def _collect_upgrade_revisions(
        self,
        upper: _RevisionIdentifierType,
        lower: _RevisionIdentifierType,
        inclusive: bool,
        implicit_base: bool,
        assert_relative_length: bool,
    ) -> tuple[set[Revision], tuple[Revision, ...]]:
        """
        Compute the set of required revisions specified by :upper, and the
        current set of active revisions specified by :lower. Find the
        difference between the two to compute the required upgrades.

        :inclusive=True includes the current/lower revisions in the set

View on GitHub (pinned to 5551b5d35f)