sqlalchemy/alembic · error · RevisionError

Relative revision %s didn't produce %d migrations

Error message

Relative revision %s didn't produce %d migrations

What it means

Raised by _parse_downgrade_target when the target is a relative downgrade of the form '+n' with no explicit symbol (i.e., 'downgrade to current + n'). Downgrading forward is nonsensical, so the request cannot produce migrations. It is a RevisionError.

Source

Thrown at alembic/script/revision.py:1116

        is a string from the command specifying the branch to consider (or
        None if no branch given), and target_revision is a Revision object
        which the command refers to. target_revisions is None if the command
        refers to 'base'. The target may be specified in absolute form, or
        relative to :current_revisions.
        """
        if target is None:
            return None, None
        assert isinstance(
            target, str
        ), "Expected downgrade target in string form"
        match = _relative_destination.match(target)
        if match:
            branch_label, symbol, relative = match.groups()
            rel_int = int(relative)
            if rel_int >= 0:
                if symbol is None:
                    # Downgrading to current + n is not valid.
                    raise RevisionError(
                        "Relative revision %s didn't "
                        "produce %d migrations" % (relative, abs(rel_int))
                    )
                # Find target revision relative to given symbol.
                rev = self._walk(
                    symbol,
                    rel_int,
                    branch_label,
                    no_overwalk=assert_relative_length,
                )
                if rev is None:
                    raise RevisionError("Walked too far")
                return branch_label, rev
            else:
                relative_revision = symbol is None
                if relative_revision:
                    # Find target revision relative to current state.
                    if branch_label:

View on GitHub (pinned to 44fb345033)

Solutions

  1. Use a negative offset for downgrades: 'alembic downgrade -2'.
  2. Use 'alembic upgrade +2' for forward relative moves.
  3. Pass an absolute revision id to remove sign ambiguity.

Example fix

# before
alembic downgrade +2   # invalid: downgrade cannot go forward
# after
alembic downgrade -2
Defensive patterns

Strategy: validation

Validate before calling

import re
if re.match(r'\+\d+$', target):
    raise ValueError(
        'use a negative offset for downgrade, not %r' % target
    )

Try / catch

from alembic.script.revision import RevisionError
try:
    command.downgrade(cfg, target)
except RevisionError as e:
    if "didn't produce" in str(e) and target.startswith('+'):
        target = target.replace('+', '-', 1)  # fix sign, retry as downgrade

Prevention

When it happens

Trigger: Issuing 'alembic downgrade +2' (or 'upgrade' target parsed in downgrade context with a positive offset and no symbol). The relative regex matched a '+' form with no leading revision symbol.

Common situations: Confusing upgrade vs downgrade semantics; passing the wrong sign in a downgrade command; tooling that builds relative targets generically.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/58bab4f029351d26.json. Report an issue: GitHub.