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
- Run 'alembic current' to see what revision the database is actually at.
- Run 'alembic history' to confirm the target revision exists in the active lineage.
- If the target is on a different branch, switch to the correct branch or merge branches first.
- 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
- Run 'alembic current' before any downgrade to verify the DB state.
- Keep a reference of valid revision ids for each environment.
- Avoid downgrading across branches — merge first.
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
- Not a valid downgrade target from current heads
- Character(s) ' ' not allowed in revision identifier
- Dependency self-loop is detected in revisions
- Self-loop is detected in revisions
- A plugin named is already registered
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)