FuelLabs/sway · error · anyhow::Error

dependency cycle detected: {}

Error message

dependency cycle detected: {}

What it means

Computing the compilation order requires a topological sort of the package graph; a cycle makes that impossible and forc reports each cycle as an arrow chain (a -> b -> ... -> a). At least one package transitively depends on itself, so no valid build order exists.

Source

Thrown at forc-pkg/src/pkg.rs:1242

        scc.iter()
            .filter(|path| path.len() > 1)
            .for_each(|cyclic_path| {
                // We are sure that there is an element in cyclic_path vec.
                let starting_node = &graph[*cyclic_path.last().unwrap()];

                // Adding first node of the path
                path.push_str(&starting_node.name.to_string());
                path.push_str(" -> ");

                for (node_index, node) in cyclic_path.iter().enumerate() {
                    path.push_str(&graph[*node].name.to_string());
                    if node_index != cyclic_path.len() - 1 {
                        path.push_str(" -> ");
                    }
                }
                path.push('\n');
            });
        anyhow!("dependency cycle detected: {}", path)
    })
}

/// Given a graph collects ManifestMap while taking in to account that manifest can be a
/// ManifestFile::Workspace. In the case of a workspace each pkg manifest map is collected and
/// their added node lists are merged.
fn graph_to_manifest_map(manifests: &MemberManifestFiles, graph: &Graph) -> Result<ManifestMap> {
    let mut manifest_map = HashMap::new();
    for pkg_manifest in manifests.values() {
        let pkg_name = &pkg_manifest.project.name;
        manifest_map.extend(pkg_graph_to_manifest_map(manifests, pkg_name, graph)?);
    }
    Ok(manifest_map)
}

/// Given a graph of pinned packages and the project manifest, produce a map containing the
/// manifest of for every node in the graph.
///

View on GitHub (pinned to 47e5e902fa)

Solutions

  1. Follow the arrow chain in the message to identify the exact packages in the loop.
  2. Break the cycle by removing one direction of the dependency, or move the shared items into a third package that both depend on.

Example fix

# before: a -> b -> a
# a/Forc.toml: [dependencies] b = { path = "../b" }
# b/Forc.toml: [dependencies] a = { path = "../a" }

# after: both depend on c
# a/Forc.toml: [dependencies] c = { path = "../c" }
# b/Forc.toml: [dependencies] c = { path = "../c" }
Defensive patterns

Strategy: validation

Validate before calling

fn has_cycle(deps: &std::collections::HashMap<String, Vec<String>>, node: &str, seen: &mut std::collections::HashSet<String>, stack: &mut std::collections::HashSet<String>) -> bool {
    if stack.contains(node) { return true; }
    if !seen.insert(node.to_string()) { return false; }
    stack.insert(node.to_string());
    let cyclic = deps.get(node).map(|cs| cs.iter().any(|c| has_cycle(deps, c, seen, stack))).unwrap_or(false);
    stack.remove(node);
    cyclic
}
// build 'deps' from each manifest's dependency names, then check every member root

Prevention

When it happens

Trigger: Two path dependencies whose Forc.toml files reference each other; workspace members mutually depending on one another; a contract dependency chain that loops back (A depends on B, B depends on A); a package listing itself as a dependency.

Common situations: Extracting shared code into a crate while leaving circular references behind; refactoring workspace members and flipping a dependency direction without removing the old one.

Related errors


AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16). Data as JSON: /api/errors/8c63a9fcaeb68b7d. Report an issue: GitHub.