{"id":"58bab4f029351d26","repo":"sqlalchemy/alembic","slug":"relative-revision-s-didn-t-produce-d-migrations","errorCode":null,"errorMessage":"Relative revision %s didn't produce %d migrations","messagePattern":"Relative revision (.+?) didn't produce (.+?) migrations","errorType":"exception","errorClass":"RevisionError","httpStatus":null,"severity":"error","filePath":"alembic/script/revision.py","lineNumber":1116,"sourceCode":"        is a string from the command specifying the branch to consider (or\n        None if no branch given), and target_revision is a Revision object\n        which the command refers to. target_revisions is None if the command\n        refers to 'base'. The target may be specified in absolute form, or\n        relative to :current_revisions.\n        \"\"\"\n        if target is None:\n            return None, None\n        assert isinstance(\n            target, str\n        ), \"Expected downgrade target in string form\"\n        match = _relative_destination.match(target)\n        if match:\n            branch_label, symbol, relative = match.groups()\n            rel_int = int(relative)\n            if rel_int >= 0:\n                if symbol is None:\n                    # Downgrading to current + n is not valid.\n                    raise RevisionError(\n                        \"Relative revision %s didn't \"\n                        \"produce %d migrations\" % (relative, abs(rel_int))\n                    )\n                # Find target revision relative to given symbol.\n                rev = self._walk(\n                    symbol,\n                    rel_int,\n                    branch_label,\n                    no_overwalk=assert_relative_length,\n                )\n                if rev is None:\n                    raise RevisionError(\"Walked too far\")\n                return branch_label, rev\n            else:\n                relative_revision = symbol is None\n                if relative_revision:\n                    # Find target revision relative to current state.\n                    if branch_label:","sourceCodeStart":1098,"sourceCodeEnd":1134,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/script/revision.py#L1098-L1134","documentation":"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.","triggerScenarios":"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.","commonSituations":"Confusing upgrade vs downgrade semantics; passing the wrong sign in a downgrade command; tooling that builds relative targets generically.","solutions":["Use a negative offset for downgrades: 'alembic downgrade -2'.","Use 'alembic upgrade +2' for forward relative moves.","Pass an absolute revision id to remove sign ambiguity."],"exampleFix":"# before\nalembic downgrade +2   # invalid: downgrade cannot go forward\n# after\nalembic downgrade -2","handlingStrategy":"validation","validationCode":"import re\nif re.match(r'\\+\\d+$', target):\n    raise ValueError(\n        'use a negative offset for downgrade, not %r' % target\n    )","typeGuard":null,"tryCatchPattern":"from alembic.script.revision import RevisionError\ntry:\n    command.downgrade(cfg, target)\nexcept RevisionError as e:\n    if \"didn't produce\" in str(e) and target.startswith('+'):\n        target = target.replace('+', '-', 1)  # fix sign, retry as downgrade","preventionTips":["Use '-' offsets for downgrade and '+' for upgrade.","Prefer absolute revision ids to avoid sign confusion.","Validate relative-target signs before issuing commands."],"tags":["relative-revision","downgrade","invalid-syntax","alembic"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}