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
- Follow the arrow chain in the message to identify the exact packages in the loop.
- 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 extracting shared code, introduce a new bottom-level crate instead of cross-linking two existing ones.
- After flipping a dependency direction, grep all Forc.toml files for the reverse edge before building.
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
- Couldn't find manifest file for {}
- failed to construct path for dependency {:?}: {}
- Failed to find path root: `path` dependency \"{}\" has no pa
- found dep {} without node entry in graph
- the dependency `{}` could not be found in `{}`
AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16).
Data as JSON: /api/errors/8c63a9fcaeb68b7d.
Report an issue: GitHub.