{"record":{"id":"0fa89009e1793195","repo":"sqlalchemy/alembic","slug":"dependency-cycle-is-detected-in-revisions-s","errorCode":null,"errorMessage":"Dependency cycle is detected in revisions (%s)","messagePattern":"Dependency cycle is detected in revisions \\((.+?)\\)","errorType":"exception","errorClass":"DependencyCycleDetected","httpStatus":null,"severity":"critical","filePath":"alembic/script/revision.py","lineNumber":294,"sourceCode":"            for rev in self._iterate_related_revisions(\n                lambda r: r._versioned_down_revisions,\n                heads,\n                map_=cast(_RevisionMapType, rev_map),\n            )\n        }.intersection(\n            rev.revision\n            for rev in self._iterate_related_revisions(\n                lambda r: r.nextrev,\n                bases,\n                map_=cast(_RevisionMapType, rev_map),\n            )\n        )\n        deleted_revs = set(rev_map.keys()) - total_space\n        if deleted_revs:\n            raise CycleDetected(sorted(deleted_revs))\n\n        if not _real_heads or not _real_bases:\n            raise DependencyCycleDetected(list(rev_map))\n        total_space = {\n            rev.revision\n            for rev in self._iterate_related_revisions(\n                lambda r: r._all_down_revisions,\n                _real_heads,\n                map_=cast(_RevisionMapType, rev_map),\n            )\n        }.intersection(\n            rev.revision\n            for rev in self._iterate_related_revisions(\n                lambda r: r._all_nextrev,\n                _real_bases,\n                map_=cast(_RevisionMapType, rev_map),\n            )\n        )\n        deleted_revs = set(rev_map.keys()) - total_space\n        if deleted_revs:\n            raise DependencyCycleDetected(sorted(deleted_revs))","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/script/revision.py#L276-L312","documentation":"Raised as DependencyCycleDetected (subclass of CycleDetected, revision.py:100-104) by _detect_cycles (revision.py:293-294) when there are no _real_heads or _real_bases in the dependency graph (built from _all_down_revisions, which includes BOTH down_revision AND the depends_on list). Unlike 36/37 (pure down_revision), this covers inter-revision `depends_on` dependencies. With no real terminus the dependency graph is cyclic.","triggerScenarios":"Two revisions declare depends_on that point at each other (A depends_on B, B depends_on A); or a depends_on chain that loops back. The dependency-only traversal (ignoring the linear down_revision) finds no head/base, so the whole set is flagged as a dependency cycle.","commonSituations":"Multi-branch projects using `depends_on` in revision directives; merge dependencies wired incorrectly; hand-authored revisions that cross-reference each other for ordering.","solutions":["Inspect each reported revision's `depends_on` argument and break the mutual dependency.","Ensure depends_on references strictly earlier revisions (or branches) and never forms a loop.","Run `alembic heads` / `alembic history` after fixing to confirm the dependency graph is acyclic."],"exampleFix":"// before\nrevision = 'aaa'\ndown_revision = 'base'\ndepends_on = ['bbb']\n# revision bbb: depends_on = ['aaa']  # mutual cycle\n// after\nrevision = 'aaa'\ndown_revision = 'base'\ndepends_on = None  # or a strictly-earlier revision","handlingStrategy":"validation","validationCode":"# Detect cycles in the depends_on dependency graph.\nfrom alembic.script import ScriptDirectory\n\ndef detect_dependency_cycle(config):\n    sd = ScriptDirectory.from_config(config)\n    revs = {r.revision: r for r in sd.walk_revisions()}\n    color = {}\n    def dfs(r):\n        if color.get(r) == 'gray':\n            raise ValueError(f'Dependency cycle at {r}')\n        if color.get(r) == 'black':\n            return\n        color[r] = 'gray'\n        for dep in getattr(revs[r], 'depends_on', None) or ():\n            if dep in revs:\n                dfs(dep)\n        color[r] = 'black'\n    for r in revs:\n        dfs(r)\n\ndetect_dependency_cycle(config)","typeGuard":"def depends_on_is_acyclic(revs: dict) -> bool:\n    color = {}\n    def dfs(r):\n        if color.get(r) == 'gray':\n            return False\n        if color.get(r) == 'black':\n            return True\n        color[r] = 'gray'\n        for dep in getattr(revs[r], 'depends_on', None) or ():\n            if dep in revs and not dfs(dep):\n                return False\n        color[r] = 'black'\n        return True\n    return all(dfs(r) for r in revs)","tryCatchPattern":"from alembic.script.revision import DependencyCycleDetected\n\ntry:\n    alembic.command.upgrade(config, 'head')\nexcept DependencyCycleDetected as e:\n    print('Dependency cycle in depends_on among:', e.revisions)","preventionTips":["Point depends_on only at strictly earlier revisions or branches, never mutual peers.","Review merge revisions' depends_on lists before committing.","Run `alembic heads` after adding depends_on to ensure the dependency DAG resolves."],"tags":["alembic","revisions","cycle","depends-on","migration-graph"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}