GoogleContainerTools/skaffold · error

cycle detected involving release %q

Error message

cycle detected involving release %q

What it means

After building the release dependency graph, skaffold runs a DFS cycle check. If a release depends (directly or transitively) on itself — e.g. A depends on B and B depends on A — topological ordering is impossible and this error is returned naming one release in the cycle.

Source

Thrown at pkg/skaffold/deploy/helm/dependencygraph.go:115

		return nil
	}

	visited := make(map[string]bool)
	recStack := make(map[string]bool)

	var checkCycle func(node string) error
	checkCycle = func(node string) error {
		if !visited[node] {
			visited[node] = true
			recStack[node] = true

			for _, dep := range g.graph[node] {
				if !visited[dep] {
					if err := checkCycle(dep); err != nil {
						return err
					}
				} else if recStack[dep] {
					return fmt.Errorf("cycle detected involving release %q", node)
				}
			}
		}
		recStack[node] = false
		return nil
	}

	for node := range g.graph {
		if !visited[node] {
			if err := checkCycle(node); err != nil {
				return err
			}
		}
	}
	return nil
}

// getNames returns a slice of release names in their original order

View on GitHub (pinned to a1189de023)

Solutions

  1. Break the cycle: remove or reorder one dependsOn link so dependencies form a DAG (e.g. make B not depend on A)
  2. Split shared functionality into a new chart/release that both conflicting releases depend on, instead of depending on each other
  3. Review each release's dependsOn list for self-references (A depending on A)

Example fix

// before (cycle)
releases:
  - name: a
    dependsOn: ["b"]
  - name: b
    dependsOn: ["a"]

// after (acyclic)
releases:
  - name: a
    chartPath: charts/a
  - name: b
    dependsOn: ["a"]
    chartPath: charts/b
Defensive patterns

Strategy: validation

Validate before calling

func hasCycle(releases []Release) bool {
    graph := map[string][]string{}
    for _, r := range releases { graph[r.Name] = r.DependsOn }
    var visit func(n string, seen, stack map[string]bool) bool
    visit = func(n string, seen, stack map[string]bool) bool {
        if stack[n] { return true }
        if seen[n] { return false }
        seen[n], stack[n] = true, true
        for _, d := range graph[n] { if visit(d, seen, stack) { return true } }
        stack[n] = false
        return false
    }
    seen, stack := map[string]bool{}, map[string]bool{}
    for n := range graph { if visit(n, seen, stack) { return true } }
    return false
}

Prevention

When it happens

Trigger: Mutual or circular `dependsOn` entries among `deploy.helm.releases`, e.g. release A depends on B while B depends on A (or A depends on A).

Common situations: Copy-paste of dependsOn blocks creating a loop; refactoring charts where dependencies were reversed; accidentally self-referencing a release's own name.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/e6e0b47dae6d36ba. Report an issue: GitHub.