FuelLabs/sway · error · anyhow::Error
more than one root package detected in graph
Error message
more than one root package detected in graph
What it means
While BFS-walking the graph to build the manifest map, a visited dependency node had no incoming edge from an already-mapped parent. For the traversal to work, every non-root node must be reachable from the single project root - hitting this means the graph contains more than one root package's subtree (e.g. extra roots left by validation/pruning), making the parent ambiguous.
Source
Thrown at forc-pkg/src/pkg.rs:1297
let proj_id = graph[proj_node].id();
manifest_map.insert(proj_id, proj_manifest.clone());
// Resolve all parents before their dependencies as we require the parent path to construct the
// dependency path. Skip the already added project node at the beginning of traversal.
let mut bfs = Bfs::new(graph, proj_node);
bfs.next(graph);
while let Some(dep_node) = bfs.next(graph) {
// Retrieve the parent node whose manifest is already stored.
let (parent_manifest, dep_name) = graph
.edges_directed(dep_node, Direction::Incoming)
.find_map(|edge| {
let parent_node = edge.source();
let dep_name = &edge.weight().name;
let parent = &graph[parent_node];
let parent_manifest = manifest_map.get(&parent.id())?;
Some((parent_manifest, dep_name))
})
.ok_or_else(|| anyhow!("more than one root package detected in graph"))?;
let dep_path = dep_path(graph, parent_manifest, dep_node, manifests).map_err(|e| {
anyhow!(
"failed to construct path for dependency {:?}: {}",
dep_name,
e
)
})?;
let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
let dep = &graph[dep_node];
manifest_map.insert(dep.id(), dep_manifest);
}
Ok(manifest_map)
}
/// Given a `graph`, the node index of a path dependency within that `graph`, and the supposed
/// `path_root` of the path dependency, ensure that the `path_root` is valid.
///View on GitHub (pinned to 47e5e902fa)
Solutions
- Regenerate Forc.lock (delete it or run 'forc update') and rebuild.
- Verify member names are unique and each member is reachable only as declared.
- If reproducible with a clean lock, capture the manifests and report upstream.
Example fix
# before forc build # more than one root package detected in graph # after rm Forc.lock && forc build
Defensive patterns
Strategy: try-catch
Validate before calling
// sanity check before planning: exactly one root per member subgraph
let roots: Vec<_> = graph.externals(petgraph::Direction::Incoming).collect();
if roots.len() > member_manifests.len() {
// extra roots present: regenerate Forc.lock before building
} Try / catch
match build_plan_result {
Ok(plan) => { /* ... */ }
Err(e) if e.to_string().contains("more than one root package") => {
let _ = std::fs::remove_file(&lock_path);
// rebuild once from manifests; escalate if it repeats
}
Err(e) => return Err(e),
} Prevention
- Avoid duplicate member names, which let subtrees merge under multiple roots.
- Regenerate the lock after removing members or dependencies so pruned nodes do not linger.
When it happens
Trigger: The graph retains additional root nodes besides the project (multiple members acting as roots feeding shared dependencies); graph shaped unexpectedly after stale-lock validation or hand edits; duplicate package names causing nodes to be shared between roots.
Common situations: Workspace restructures with a stale Forc.lock; duplicated member names; partially pruned graphs after dependency removal.
Related errors
- graph contains no project node
- graph contains more than one project node
- Couldn't find manifest file for {}
- failed to construct path for dependency {:?}: {}
- Cannot find manifest for {}
AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16).
Data as JSON: /api/errors/cb2582745ad7c7d0.
Report an issue: GitHub.