argoproj/argo-workflows · error
graph with cycle
Error message
graph with cycle
What it means
TopologicalSorting detects cycles with a Kahn-style queue: after seeding, the queue is pre-allocated to len(graph), and any nil slot means fewer nodes than expected reached zero indegree — i.e. some nodes form a cycle and can never be scheduled. The function returns this fixed error to report an unsatisfiable dependency graph.
Source
Thrown at util/sorting/topological_sorting.go:44
return nil, fmt.Errorf("invalid dependency %s", dependency)
}
nextNodeMap[dependency] = append(nextNodeMap[dependency], node.NodeName)
}
}
queue := make([]*TopologicalSortingNode, len(graph))
head, tail := 0, 0
for nodeName, priorNodeCount := range priorNodeCountMap {
if priorNodeCount == 0 {
queue[tail] = nodeNameMap[nodeName]
tail++
}
}
for head < len(queue) {
curr := queue[head]
if curr == nil {
return nil, fmt.Errorf("graph with cycle")
}
for _, next := range nextNodeMap[curr.NodeName] {
if priorNodeCountMap[next] > 0 {
if priorNodeCountMap[next] == 1 {
queue[tail] = nodeNameMap[next]
tail++
}
priorNodeCountMap[next]--
}
}
head++
}
return queue, nil
}
View on GitHub (pinned to 35bff19146)
Solutions
- Break the cycle: audit Dependencies and remove or invert one edge (e.g. A→B and B→A means dropping one direction).
- Check for self-dependencies — a node listing its own NodeName in Dependencies.
- Run a cycle-detection pass (DFS with a visiting set) on the graph before calling TopologicalSorting to produce a better message naming the offending nodes.
- If edges are derived from template definitions, fix the workflow spec's depends clauses; the sort input mirrors them.
Example fix
// before
nodes := []*sorting.TopologicalSortingNode{
{NodeName: "A", Dependencies: []string{"B"}},
{NodeName: "B", Dependencies: []string{"A"}}, // cycle
}
// after
nodes := []*sorting.TopologicalSortingNode{
{NodeName: "A"},
{NodeName: "B", Dependencies: []string{"A"}},
} Defensive patterns
Strategy: validation
Validate before calling
func hasCycle(graph []*sorting.TopologicalSortingNode) bool {
state := map[string]int{} // 0 unvisited, 1 visiting, 2 done
var visit func(string) bool
visit = func(n string) bool {
switch state[n] {
case 1: return true
case 2: return false
}
state[n] = 1
for _, node := range graph {
if node.NodeName != n { continue }
for _, d := range node.Dependencies {
if visit(d) { return true }
}
}
state[n] = 2
return false
}
for _, n := range graph { if visit(n.NodeName) { return true } }
return false
} Try / catch
sorted, err := sorting.TopologicalSorting(graph)
if err != nil {
if err.Error() == "graph with cycle" {
cycle := findCycleNodes(graph) // report specifics to the user
return fmt.Errorf("workflow dependency cycle involving: %v", cycle)
}
return err
} Prevention
- Validate the workflow DAG spec (depends edges) for cycles before building the sort graph.
- Never add a node's own name to its Dependencies; guard self-references where edges are generated.
- Reuse the controller/validator's cycle detection on template specs so bad specs fail at submission time.
When it happens
Trigger: Passing a graph whose Dependencies form a cycle, e.g. A depends on B and B depends on A (or a self-dependency A→A), so no node in the cycle ever reaches indegree 0 and the queue fills with nils.
Common situations: DAG templates with circular depends/after references between tasks; synchronization edges added in both directions by accident; self-referencing node built from a name that includes itself; test fixtures GraphWithCycle/GraphWithCycle2 exercising this path.
Related errors
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/145957924da827dd.
Report an issue: GitHub.