pulumi/pulumi · error

cycle found: %s

Error message

cycle found: %s

What it means

FormatCyclicInstallError formats a detected dependency cycle from an install/upgrade graph (e.g. plugin or package dependency nodes) into a single error listing the cycle path as node@version entries joined by ' -> '. The underlying cycle detection ran first; this error is the human-readable report.

Source

Thrown at pkg/cmd/pulumi/diag/diag.go:58

func FormatCyclicInstallError(
	ctx context.Context, err packageinstallation.ErrorCyclicDependencies,
	wd string,
) error {
	cyclePath := make([]string, len(err.Cycle))
	for i, n := range err.Cycle {
		name := n.Name
		if plugin.IsLocalPluginPath(ctx, n.Name) {
			rel, err := filepath.Rel(wd, n.Name)
			if err == nil {
				name = rel
			}
		}
		if n.Version != nil {
			name += "@" + n.Version.String()
		}
		cyclePath[i] = name
	}
	return fmt.Errorf("cycle found: %s", strings.Join(cyclePath, " -> "))
}

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Inspect the printed cycle path and break the loop by removing one dependency edge
  2. Pin different versions so the dependency is one-directional, or split the shared functionality into a third package
  3. Regenerate/update the requirement or lock file after fixing the graph

Example fix

// before
pkgA@1.0.0 -> pkgB@2.0.0 -> pkgA@1.0.0  (cycle)
// after
require pkgB at >=2.1.0 which no longer depends on pkgA, or drop pkgA's dependency on pkgB
Defensive patterns

Strategy: try-catch

Validate before calling

// build the dependency graph and topologically sort before install
// if topoSort throws on a node already in the current stack, you have a cycle

Type guard

function hasCycle(graph) { /* DFS with rec-stack; return the cycle path or null */ }

Try / catch

try { install(deps) } catch (e) { if (e.message.startsWith('cycle found:')) { console.error('break one of:', e.message); } else throw e }

Prevention

When it happens

Trigger: Installing or resolving components whose dependency graph contains a cycle, e.g. A depends on B and B depends on A (directly or through several hops).

Common situations: Hand-written plugin requirements that mutually reference each other; version pinning changes that introduce a round trip in the graph; publishing two packages each depending on the other.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/5cd896e36d1bd938. Report an issue: GitHub.