sqlalchemy/alembic · critical · DependencyCycleDetected
Dependency cycle is detected in revisions (%s)
Error message
Dependency cycle is detected in revisions (%s)
What it means
Raised as DependencyCycleDetected (subclass of CycleDetected) in _detect_cycles() when, after wiring depends_on links, there are no real heads or no real bases (the dependency subgraph has no endpoints). This signals a cycle purely among the depends_on dependencies declared on revisions, independent of the down_revision lineage. The message lists the revisions involved.
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 44fb345033)
Solutions
- Audit the depends_on attributes of the listed revisions and break the cycle.
- Use 'alembic history' and inspect each revision's depends_on to find the loop edge.
- Re-declare depends_on as a strict DAG (no reciprocal dependencies).
Example fix
// before # revision A: depends_on='B' # revision B: depends_on='A' # cycle // after # revision A: depends_on='B' # revision B: depends_on=None
Defensive patterns
Strategy: validation
Validate before calling
def depends_on_acyclic(dep_map):
# dep_map: dict revision_id -> tuple of depends_on ids
WHITE, GRAY, BLACK = 0, 1, 2
color = {r: WHITE for r in dep_map}
def visit(node):
color[node] = GRAY
for dep in (dep_map.get(node) or ()):
if color.get(dep) == GRAY:
return True
if color.get(dep) == WHITE and visit(dep):
return True
color[node] = BLACK
return False
return not any(color[r] == WHITE and visit(r) for r in dep_map) Try / catch
try:
script_dir.get_revision('heads')
except Exception as e:
if 'Dependency cycle' in str(e):
# audit depends_on edges on listed revisions
...
else:
raise Prevention
- Declare depends_on as a strict DAG (no reciprocal dependencies).
- Avoid depending on a revision that already depends on the current one.
- Review depends_on values after rebasing branched migrations.
When it happens
Trigger: Two revisions where revision A lists revision B in depends_on and B lists A in depends_on; a longer depends_on loop (A->B->C->A); a revision depending on itself via depends_on.
Common situations: Manually setting depends_on on revisions to enforce ordering and accidentally forming a loop; cross-branch dependencies declared symmetrically; generated depends_on values that reference each other.
Related errors
- Cycle is detected in revisions (%s)
- Branch name '%s' in revision %s already used by revision %s
- No such constraint: '%s'
- SQLAlchemy 2.0 required
- Dependency self-loop is detected in revisions (%s)
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/0fa89009e1793195.json.
Report an issue: GitHub.