{"record":{"id":"13ef770942a4ff2e","repo":"sqlalchemy/alembic","slug":"self-loop-is-detected-in-revisions-s","errorCode":null,"errorMessage":"Self-loop is detected in revisions (%s)","messagePattern":"Self-loop is detected in revisions \\((.+?)\\)","errorType":"exception","errorClass":"LoopDetected","httpStatus":null,"severity":"error","filePath":"alembic/script/revision.py","lineNumber":1592,"sourceCode":"\n    @classmethod\n    def verify_rev_id(cls, revision: str) -> None:\n        illegal_chars = set(revision).intersection(_revision_illegal_chars)\n        if illegal_chars:\n            raise RevisionError(\n                \"Character(s) '%s' not allowed in revision identifier '%s'\"\n                % (\", \".join(sorted(illegal_chars)), revision)\n            )\n\n    def __init__(\n        self,\n        revision: str,\n        down_revision: str | tuple[str, ...] | None,\n        dependencies: str | tuple[str, ...] | None = None,\n        branch_labels: str | tuple[str, ...] | None = None,\n    ) -> None:\n        if down_revision and revision in util.to_tuple(down_revision):\n            raise LoopDetected(revision)\n        elif dependencies is not None and revision in util.to_tuple(\n            dependencies\n        ):\n            raise DependencyLoopDetected(revision)\n\n        self.verify_rev_id(revision)\n        self.revision = revision\n        self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision))\n        self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies))\n        self._orig_branch_labels = util.to_tuple(branch_labels, default=())\n        self.branch_labels = set(self._orig_branch_labels)\n\n    def __repr__(self) -> str:\n        args = [repr(self.revision), repr(self.down_revision)]\n        if self.dependencies:\n            args.append(\"dependencies=%r\" % (self.dependencies,))\n        if self.branch_labels:\n            args.append(\"branch_labels=%r\" % (self.branch_labels,))","sourceCodeStart":1574,"sourceCodeEnd":1610,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/script/revision.py#L1574-L1610","documentation":"Raised as LoopDetected when a Revision's down_revision contains its own revision id, creating a self-referential loop (revision.py:1591-1592). A revision cannot be its own parent — this would make the migration history untraversable. The check runs during Revision.__init__ before the revision is registered.","triggerScenarios":"Constructing a Revision where revision == down_revision (directly or within a tuple). Occurs at revision.py:1591: 'if down_revision and revision in util.to_tuple(down_revision)'. Triggered by hand-editing a migration script's revision/down_revision to the same value, or a buggy revision id generator.","commonSituations":"Copy-pasting a migration file and forgetting to change the revision id while setting down_revision to the copied id. Automated tools that generate both fields from the same source. Typo where revision and down_revision are swapped to the same hash.","solutions":["Open the offending migration script and verify revision and down_revision are different values.","Generate a fresh revision id: alembic revision -m 'description' produces a unique id automatically.","If the revision was copied, ensure revision gets a new unique id and down_revision points to the actual parent.","Run 'alembic history' after fixing to confirm the lineage is linear and loop-free."],"exampleFix":"# before\nrevision = 'abc123'\ndown_revision = 'abc123'  # self-loop!\n\n# after\nrevision = 'def456'\ndown_revision = 'abc123'","handlingStrategy":"validation","validationCode":"def has_no_self_loop(revision, down_revision):\n    from alembic.util import to_tuple\n    if down_revision is None:\n        return True\n    return revision not in to_tuple(down_revision)","typeGuard":null,"tryCatchPattern":"from alembic.script.revision import LoopDetected\n\ntry:\n    # construct or register revision\n    script_dir.revision_map.add_revision(my_revision)\nexcept LoopDetected as e:\n    print(f\"Self-loop: revision {e.revisions} points to itself\")\n    raise","preventionTips":["Never copy-paste a migration file and keep the same revision id as down_revision.","After creating a migration, verify revision != down_revision.","Use 'alembic revision' command rather than hand-copying files."],"tags":["alembic","migrations","revision-graph","cycle","validation"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}