elsa-workflows/elsa-core · error · InvalidOperationException

Circular dependency detected involving task type

Error message

Circular dependency detected involving task type: {node.Name}

What it means

TopologicalTaskSorter orders task types by their dependencies using DFS. Visit tracks nodes on the current recursion stack in 'visiting'; encountering a node already on the stack means a dependency cycle, so it throws this InvalidOperationException naming the task type where the cycle closed.

Solutions

  1. Inspect the named task type's dependency registration and remove the entry that closes the cycle.
  2. Break the cycle by extracting the shared work into a third task both depend on.
  3. Log the full visiting stack at the throw point to see the whole cycle path.
  4. Validate the dependency graph at startup in a dev-only check before sorting.

Example fix

// before
A.DependsOn = [B]; B.DependsOn = [A];
// after
A.DependsOn = [C]; B.DependsOn = [C]; // C has no dependencies
Defensive patterns

Strategy: try-catch

Validate before calling

bool HasCycle(IEnumerable<(Type Task, Type[] DependsOn)> edges)
{
    var graph = edges.ToDictionary(e => e.Task, e => e.DependsOn.ToList());
    var state = new Dictionary<Type, int>(); // 0=unvisited 1=visiting 2=done
    bool Dfs(Type n) {
        if (state.TryGetValue(n, out var s)) return s == 1;
        state[n] = 1;
        foreach (var d in graph.GetValueOrDefault(n, new List<Type>())) if (Dfs(d)) return true;
        state[n] = 2; return false;
    }
    return graph.Keys.Any(Dfs);
}

Try / catch

try { var ordered = sorter.TopologicalSort(tasks); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Circular dependency detected")) { logger.LogCritical(ex, "Fix task dependency registration"); throw; }

Prevention

When it happens

Trigger: Calling TopologicalSort (directly or via Visit) with a graph where task type A depends (transitively) on itself — e.g. feature A starts after B and B starts after A, or a task lists itself as a dependency.

Common situations: Registering features/tasks with mutually referencing StartTask/DependsOn attributes; a copy-paste duplicated dependency entry; two modules each claiming the other must start first after a version upgrade.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/67af7e5a4e89cee7. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Common/Helpers/TopologicalTaskSorter.cs:74

    private static List<Type> TopologicalSort(Dictionary<Type, List<Type>> graph)
    {
        var sorted = new List<Type>();
        var visited = new HashSet<Type>();
        var visiting = new HashSet<Type>();

        foreach (var node in graph.Keys)
        {
            if (!visited.Contains(node))
                Visit(node, graph, visited, visiting, sorted);
        }

        return sorted;
    }

    private static void Visit(Type node, Dictionary<Type, List<Type>> graph, HashSet<Type> visited, HashSet<Type> visiting, List<Type> sorted)
    {
        if (visiting.Contains(node))
            throw new InvalidOperationException($"Circular dependency detected involving task type: {node.Name}");

        if (visited.Contains(node))
            return;

        visiting.Add(node);

        if (graph.TryGetValue(node, out var dependencies))
        {
            foreach (var dependency in dependencies)
            {
                Visit(dependency, graph, visited, visiting, sorted);
            }
        }

        visiting.Remove(node);
        visited.Add(node);
        sorted.Add(node);
    }

View on GitHub (pinned to fe9217bdfa)