GoogleContainerTools/skaffold · error

cycle detected in build dependencies involving %q

Error message

cycle detected in build dependencies involving %q

What it means

dfs runs a depth-first search over the artifact build-dependency graph to detect cycles. If an artifact is re-entered while still on the current DFS path (marked), the dependencies form a cycle and validation fails. Cycles are impossible to build, since each artifact would wait on another to finish.

Source

Thrown at pkg/skaffold/schema/validation/validation.go:239

		m[artifact.ImageName] = artifact
	}
	visited := make(map[string]bool)
	for i, artifact := range artifacts {
		if err := dfs(artifact, visited, make(map[string]bool), m); err != nil {
			cfgErrs = append(cfgErrs, ErrorWithLocation{
				Error:    err,
				Location: cfgs.Locate(artifacts[i]),
			})
			return
		}
	}
	return
}

// dfs runs a Depth First Search algorithm for cycle detection in a directed graph
func dfs(artifact *latest.Artifact, visited, marked map[string]bool, artifacts map[string]*latest.Artifact) error {
	if marked[artifact.ImageName] {
		return fmt.Errorf("cycle detected in build dependencies involving %q", artifact.ImageName)
	}
	marked[artifact.ImageName] = true
	defer func() {
		marked[artifact.ImageName] = false
	}()
	if visited[artifact.ImageName] {
		return nil
	}
	visited[artifact.ImageName] = true

	for _, dep := range artifact.Dependencies {
		d, found := artifacts[dep.ImageName]
		if !found {
			return fmt.Errorf("unknown build dependency %q for artifact %q", dep.ImageName, artifact.ImageName)
		}
		if err := dfs(d, visited, marked, artifacts); err != nil {
			return err
		}

View on GitHub (pinned to a1189de023)

Solutions

  1. Trace the cycle named in the error and remove one edge so dependencies form a DAG (e.g. drop A's dependency on B)
  2. Build shared code into a separate base image that both artifacts depend on, instead of cross-depending
  3. Check for an artifact listing itself in its own buildDependencies

Example fix

// before
artifacts:
  - image: app-a
    requires: [{ image: app-b }]
  - image: app-b
    requires: [{ image: app-a }]
// after
artifacts:
  - image: base-lib
  - image: app-a
    requires: [{ image: base-lib }]
  - image: app-b
    requires: [{ image: base-lib }]
Defensive patterns

Strategy: validation

Validate before calling

// Go: detect cycles in artifact dependencies before running skaffold
func hasCycle(artifacts map[string][]string) bool {
	var visit func(node string, path map[string]bool) bool
	visit = func(node string, path map[string]bool) bool {
		if path[node] {
			return true
		}
		path[node] = true
		for _, dep := range artifacts[node] {
			if visit(dep, path) {
				return true
			}
		}
		delete(path, node)
		return false
	}
	for n := range artifacts {
		if visit(n, map[string]bool{}) {
			return true
		}
	}
	return false
}

Try / catch

if hasCycle(depGraph) {
	return errors.New("build dependencies must form a DAG; remove the cycle before running skaffold")
}

Prevention

When it happens

Trigger: Artifact A declares buildDependencies on B, and B (directly or transitively) declares a dependency back on A, so dfs revisits an artifact still marked on the active path.

Common situations: Circular requires/depends_on entries after refactoring multi-artifact skaffold.yaml files, or accidentally listing the artifact itself as its own dependency.

Related errors


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