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

  1. Regenerate Forc.lock (delete it or run 'forc update') and rebuild.
  2. Verify member names are unique and each member is reachable only as declared.
  3. 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

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


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