rust-lang/cargo · error

several `[patch]` entries resolving to same version

Error message

several `[patch]` entries resolving to same version `{} v{}`
help: check `{}` patch definitions for `{}` in {}

What it means

Multiple `[patch]` entries that resolve the same package to the same `(name, version)` pair create ambiguity — Cargo cannot choose between them. After collecting all unlocked summaries, the registry builds a `HashSet` of `(name, version)` and bails on the first duplicate, listing the offending patch locations.

Solutions

  1. Keep only one `[patch]` entry per (package, version) and remove duplicates from other manifests.
  2. If different patches target different versions, ensure their version requirements are disjoint.
  3. Centralize all patches in the workspace root `[patch]` table.

Example fix

# before (root Cargo.toml)
[patch.crates-io]
foo = { git = "https://example.com/a/foo" }
# (and, in a member Cargo.toml)
[patch.crates-io]
foo = { git = "https://example.com/b/foo" }

# after (single entry in root)
[patch.crates-io]
foo = { git = "https://example.com/a/foo" }
Defensive patterns

Strategy: validation

Validate before calling

use std::collections::HashSet;
fn patches_resolve_uniquely(patches: &[(String, semver::Version)]) -> bool {
    let mut seen = HashSet::new();
    patches.iter().all(|(n, v)| seen.insert((n.clone(), v.clone())))
}
// gather (name, version) for each patch target before resolution

Prevention

When it happens

Trigger: Two `[patch.crates-io] foo = ...` entries (in the root or different manifests) whose targets both yield `foo 1.2.0`. The `!name_and_version.insert((name, version))` check at registry.rs:463 fails.

Common situations: Patching the same crate from both a workspace manifest and a member manifest. Merging patches from multiple sources during a monorepo consolidation. Stale patches left after a dependency was consolidated.

Related errors


AI-assisted analysis of rust-lang/cargo@98a09e7e7d (2026-08-11). Data as JSON: /api/errors/dccb8058fd89f6bb. Report an issue: GitHub.

Appendix: source

Thrown at src/workspace/registry.rs:460

                    url,
                    orig_patch.loc
                ));
            }
            Ok(summary)
        }).collect::<CargoResult<Vec<_>>>()?;

        let mut name_and_version = HashSet::default();
        for summary in unlocked_summaries.iter() {
            let name = summary.package_id().name();
            let version = summary.package_id().version();
            if !name_and_version.insert((name, version)) {
                let duplicate_locations = patch_deps
                    .iter()
                    .filter(|&p| p.0.dep.package_name() == name)
                    .map(|p| format!("`{}`", p.0.loc))
                    .unique()
                    .join(", ");
                return Err(anyhow::anyhow!(
                    "several `[patch]` entries resolving to same version `{} v{}`\n\
                    help: check `{}` patch definitions for `{}` in {}",
                    name,
                    version,
                    name,
                    url,
                    duplicate_locations
                ));
            }
        }

        // Calculate a list of all patches available for this source.
        let mut ids = Vec::new();
        for (summary, (_, lock)) in unlocked_summaries.iter().zip(patch_deps) {
            ids.push(summary.package_id());
            // This is subtle where the list of `ids` for a canonical URL is
            // extend with possibly two ids per summary. This is done to handle
            // the transition from the v2->v3 lock file format where in v2

View on GitHub (pinned to 98a09e7e7d)