sqlalchemy/alembic · error · DependencyLoopDetected

Dependency self-loop is detected in revisions (%s)

Error message

Dependency self-loop is detected in revisions (%s)

What it means

Raised as DependencyLoopDetected (revision.py:1596, kind='Dependency self-loop', subclass of both DependencyCycleDetected and LoopDetected) during Revision.__init__ when the revision's own id appears inside its dependencies tuple. Like a down_revision self-loop, a dependency on itself is a degenerate cycle and is rejected immediately.

Source

Thrown at alembic/script/revision.py:1596

        if illegal_chars:
            raise RevisionError(
                "Character(s) '%s' not allowed in revision identifier '%s'"
                % (", ".join(sorted(illegal_chars)), revision)
            )

    def __init__(
        self,
        revision: str,
        down_revision: str | tuple[str, ...] | None,
        dependencies: str | tuple[str, ...] | None = None,
        branch_labels: str | tuple[str, ...] | None = None,
    ) -> None:
        if down_revision and revision in util.to_tuple(down_revision):
            raise LoopDetected(revision)
        elif dependencies is not None and revision in util.to_tuple(
            dependencies
        ):
            raise DependencyLoopDetected(revision)

        self.verify_rev_id(revision)
        self.revision = revision
        self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision))
        self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies))
        self._orig_branch_labels = util.to_tuple(branch_labels, default=())
        self.branch_labels = set(self._orig_branch_labels)

    def __repr__(self) -> str:
        args = [repr(self.revision), repr(self.down_revision)]
        if self.dependencies:
            args.append("dependencies=%r" % (self.dependencies,))
        if self.branch_labels:
            args.append("branch_labels=%r" % (self.branch_labels,))
        return "%s(%s)" % (self.__class__.__name__, ", ".join(args))

    def add_nextrev(self, revision: Revision) -> None:
        self._all_nextrev = self._all_nextrev.union([revision.revision])

View on GitHub (pinned to 44fb345033)

Solutions

  1. Remove the self-referential id from the dependencies tuple/list.
  2. Set dependencies to other branch heads you genuinely depend on, or omit the parameter entirely.
  3. Re-run `alembic history --verbose` to confirm dependency edges are between distinct revisions.

Example fix

# before
revision = 'abc123'
dependencies = ('abc123', 'def456')
# after
revision = 'abc123'
dependencies = ('def456',)
Defensive patterns

Strategy: validation

Validate before calling

def check_no_dependency_self_loop(revision: str, dependencies):
    deps = (dependencies,) if isinstance(dependencies, str) else (dependencies or ())
    if revision in deps:
        raise ValueError(f'revision {revision!r} cannot depend on itself')

check_no_dependency_self_loop(revision, dependencies)

Try / catch

from alembic.script.revision import DependencyLoopDetected
try:
    rev = Revision(revision, down_revision, dependencies=dependencies)
except DependencyLoopDetected as e:
    print(f'Dependency self-loop: {e}. Remove self from dependencies.')

Prevention

When it happens

Trigger: Writing a migration with revision = 'abc' and dependencies = ('abc',) or dependencies = 'abc'; programmatically constructing Revision with a dependency list that includes the revision's own id.

Common situations: Adding a cross-branch dependency and accidentally listing the current revision; merge-generated dependency lists that copy the revision id.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/a9c6c86f33477fbe.json. Report an issue: GitHub.