argoproj/argo-workflows · error

invalid dependency

Error message

invalid dependency %s

What it means

During dependency registration, TopologicalSorting verifies each dependency string refers to a node already present in the graph (by NodeName). A dependency naming a node that was never added is rejected, since the sort cannot order against a nonexistent vertex.

Solutions

  1. Ensure every string in Dependencies matches exactly one node's NodeName in the same graph slice.
  2. When filtering nodes out of the graph, also remove/redirect their dependents' dependency references.
  3. Use the same name-building helper for both NodeName and Dependencies so they cannot diverge.
  4. Fix the test fixture to include the referenced dependency node.

Example fix

// before
graph = append(graph, &sorting.TopologicalSortingNode{NodeName: "B", Dependencies: []string{"A"}})
// after: node A must also be in the graph
graph = append(graph, &sorting.TopologicalSortingNode{NodeName: "A"},
    &sorting.TopologicalSortingNode{NodeName: "B", Dependencies: []string{"A"}})
Defensive patterns

Strategy: validation

Validate before calling

func allDepsResolve(graph []*sorting.TopologicalSortingNode) error {
    names := map[string]bool{}
    for _, n := range graph { names[n.NodeName] = true }
    for _, n := range graph {
        for _, d := range n.Dependencies {
            if !names[d] { return fmt.Errorf("dep %q missing from graph", d) }
        }
    }
    return nil
}

Try / catch

if err := allDepsResolve(graph); err != nil { return err }
sorted, err := sorting.TopologicalSorting(graph)
if err != nil { return fmt.Errorf("topological sort failed: %w", err) }

Prevention

When it happens

Trigger: A TopologicalSortingNode lists a Dependency whose name is not any node's NodeName — e.g. typos, dependency added for a node that was filtered out of the graph, or boundary/exit nodes referenced without being appended.

Common situations: Constructing the graph from DAG templates where an 'outbound' synchronization node is referenced but never inserted; renaming node names in one place but not in Dependencies; pruning failed steps from the graph while leaving their dependents' references; test fixtures with dangling dependencies.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/4b6d4aef3f7315b7. Report an issue: GitHub.

Appendix: source

Thrown at util/sorting/topological_sorting.go:26

	NodeName     string
	Dependencies []string
}

func TopologicalSorting(graph []*TopologicalSortingNode) ([]*TopologicalSortingNode, error) {
	priorNodeCountMap := make(map[string]int, len(graph))               // nodeName -> priorNodeCount
	nextNodeMap := make(map[string][]string, len(graph))                // nodeName -> nextNodeList
	nodeNameMap := make(map[string]*TopologicalSortingNode, len(graph)) // nodeName -> node
	for _, node := range graph {
		if _, ok := nodeNameMap[node.NodeName]; ok {
			return nil, fmt.Errorf("duplicated nodeName %s", node.NodeName)
		}
		nodeNameMap[node.NodeName] = node
		priorNodeCountMap[node.NodeName] = len(node.Dependencies)
	}
	for _, node := range graph {
		for _, dependency := range node.Dependencies {
			if _, ok := nodeNameMap[dependency]; !ok {
				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")

View on GitHub (pinned to 35bff19146)