sqlalchemy/alembic · critical · DependencyCycleDetected
Dependency cycle is detected in revisions
Error message
Dependency cycle is detected in revisions (%s)
What it means
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.
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.
Example fix
// before revision = 'aaa' down_revision = 'base' depends_on = ['bbb'] # revision bbb: depends_on = ['aaa'] # mutual cycle // after revision = 'aaa' down_revision = 'base' depends_on = None # or a strictly-earlier revision
Defensive patterns
Strategy: validation
Validate before calling
# Detect cycles in the depends_on dependency graph.
from alembic.script import ScriptDirectory
def detect_dependency_cycle(config):
sd = ScriptDirectory.from_config(config)
revs = {r.revision: r for r in sd.walk_revisions()}
color = {}
def dfs(r):
if color.get(r) == 'gray':
raise ValueError(f'Dependency cycle at {r}')
if color.get(r) == 'black':
return
color[r] = 'gray'
for dep in getattr(revs[r], 'depends_on', None) or ():
if dep in revs:
dfs(dep)
color[r] = 'black'
for r in revs:
dfs(r)
detect_dependency_cycle(config) Type guard
def depends_on_is_acyclic(revs: dict) -> bool:
color = {}
def dfs(r):
if color.get(r) == 'gray':
return False
if color.get(r) == 'black':
return True
color[r] = 'gray'
for dep in getattr(revs[r], 'depends_on', None) or ():
if dep in revs and not dfs(dep):
return False
color[r] = 'black'
return True
return all(dfs(r) for r in revs) Try / catch
from alembic.script.revision import DependencyCycleDetected
try:
alembic.command.upgrade(config, 'head')
except DependencyCycleDetected as e:
print('Dependency cycle in depends_on among:', e.revisions) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Multi-branch projects using `depends_on` in revision directives; merge dependencies wired incorrectly; hand-authored revisions that cross-reference each other for ordering.
Related errors
- Cycle is detected in revisions
- Self-loop is detected in revisions
- A plugin named is already registered
- Can not set dispatch function for object
- Can't change down_revision on a refresh operation.
AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11).
Data as JSON: /api/errors/0fa89009e1793195.
Report an issue: GitHub.
Appendix: source
Thrown at alembic/script/revision.py:294
for rev in self._iterate_related_revisions(
lambda r: r._versioned_down_revisions,
heads,
map_=cast(_RevisionMapType, rev_map),
)
}.intersection(
rev.revision
for rev in self._iterate_related_revisions(
lambda r: r.nextrev,
bases,
map_=cast(_RevisionMapType, rev_map),
)
)
deleted_revs = set(rev_map.keys()) - total_space
if deleted_revs:
raise CycleDetected(sorted(deleted_revs))
if not _real_heads or not _real_bases:
raise DependencyCycleDetected(list(rev_map))
total_space = {
rev.revision
for rev in self._iterate_related_revisions(
lambda r: r._all_down_revisions,
_real_heads,
map_=cast(_RevisionMapType, rev_map),
)
}.intersection(
rev.revision
for rev in self._iterate_related_revisions(
lambda r: r._all_nextrev,
_real_bases,
map_=cast(_RevisionMapType, rev_map),
)
)
deleted_revs = set(rev_map.keys()) - total_space
if deleted_revs:
raise DependencyCycleDetected(sorted(deleted_revs))View on GitHub (pinned to 5551b5d35f)