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
- Trace the cycle named in the error and remove one edge so dependencies form a DAG (e.g. drop A's dependency on B)
- Build shared code into a separate base image that both artifacts depend on, instead of cross-depending
- 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
- Draw the artifact dependency graph when adding new requires entries
- Never let an artifact depend (directly or transitively) on itself
- Extract shared components into a dedicated base artifact to break mutual dependencies
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
- unknown build dependency %q for artifact %q
- verify command expects non-zero number of test cases
- CONFIG_MISSING_MANIFEST_FILE_ERR
- INIT_CLOUD_RUN_LOCATION_ERROR
- missing apiVersion
AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05).
Data as JSON: /api/errors/95ce696a6c26c25a.
Report an issue: GitHub.