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 orderView on GitHub (pinned to a1189de023)
Solutions
- Break the cycle: remove or reorder one dependsOn link so dependencies form a DAG (e.g. make B not depend on A)
- Split shared functionality into a new chart/release that both conflicting releases depend on, instead of depending on each other
- 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
- Model release dependencies as a DAG and document intended ordering
- Check for self-references when writing dependsOn entries
- Run the cycle-check above in CI config validation before deploying
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
- duplicate release name %s
- release %s depends on non-existent release %s
- unable to create dependency graph: %w
- strings.Join(errMsgs, "\n") (joined helm cleanup error messa
- Failed to install Helm plugin
AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05).
Data as JSON: /api/errors/e6e0b47dae6d36ba.
Report an issue: GitHub.