{"record":{"id":"67af7e5a4e89cee7","repo":"elsa-workflows/elsa-core","slug":"circular-dependency-detected-involving-task-type-node-name","errorCode":null,"errorMessage":"Circular dependency detected involving task type: {node.Name}","messagePattern":"Circular dependency detected involving task type: (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Common/Helpers/TopologicalTaskSorter.cs","lineNumber":74,"sourceCode":"    private static List<Type> TopologicalSort(Dictionary<Type, List<Type>> graph)\n    {\n        var sorted = new List<Type>();\n        var visited = new HashSet<Type>();\n        var visiting = new HashSet<Type>();\n\n        foreach (var node in graph.Keys)\n        {\n            if (!visited.Contains(node))\n                Visit(node, graph, visited, visiting, sorted);\n        }\n\n        return sorted;\n    }\n\n    private static void Visit(Type node, Dictionary<Type, List<Type>> graph, HashSet<Type> visited, HashSet<Type> visiting, List<Type> sorted)\n    {\n        if (visiting.Contains(node))\n            throw new InvalidOperationException($\"Circular dependency detected involving task type: {node.Name}\");\n\n        if (visited.Contains(node))\n            return;\n\n        visiting.Add(node);\n\n        if (graph.TryGetValue(node, out var dependencies))\n        {\n            foreach (var dependency in dependencies)\n            {\n                Visit(dependency, graph, visited, visiting, sorted);\n            }\n        }\n\n        visiting.Remove(node);\n        visited.Add(node);\n        sorted.Add(node);\n    }","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Common/Helpers/TopologicalTaskSorter.cs#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the named task type's dependency registration and remove the entry that closes the cycle.","Break the cycle by extracting the shared work into a third task both depend on.","Log the full visiting stack at the throw point to see the whole cycle path.","Validate the dependency graph at startup in a dev-only check before sorting."],"exampleFix":"// before\nA.DependsOn = [B]; B.DependsOn = [A];\n// after\nA.DependsOn = [C]; B.DependsOn = [C]; // C has no dependencies","handlingStrategy":"try-catch","validationCode":"bool HasCycle(IEnumerable<(Type Task, Type[] DependsOn)> edges)\n{\n    var graph = edges.ToDictionary(e => e.Task, e => e.DependsOn.ToList());\n    var state = new Dictionary<Type, int>(); // 0=unvisited 1=visiting 2=done\n    bool Dfs(Type n) {\n        if (state.TryGetValue(n, out var s)) return s == 1;\n        state[n] = 1;\n        foreach (var d in graph.GetValueOrDefault(n, new List<Type>())) if (Dfs(d)) return true;\n        state[n] = 2; return false;\n    }\n    return graph.Keys.Any(Dfs);\n}","typeGuard":null,"tryCatchPattern":"try { var ordered = sorter.TopologicalSort(tasks); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Circular dependency detected\")) { logger.LogCritical(ex, \"Fix task dependency registration\"); throw; }","preventionTips":["Keep dependency declarations one-directional per feature","Add a startup self-check that sorts the graph before serving traffic","Review DependsOn/StartTask attributes in code review for mutual references"],"tags":["topological-sort","circular-dependency","graph"],"backgroundTag":"circular-dependency-detected","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}