rust-lang/cargo · error

found patches and a path override

Error message

found patches and a path override

What it means

A dependency cannot simultaneously have `[patch]` entries and a path override — these are two distinct override mechanisms that conflict. In the `(Some(override_summary), Some(source))` arm, the registry checks `if !patches.is_empty()` and bails at registry.rs:779. Cargo refuses to mix the two to avoid ambiguous resolution semantics.

Solutions

  1. Pick one override mechanism: use `[patch]` and remove path overrides, or vice-versa.
  2. Migrate fully to `[patch]` (the supported mechanism) and delete `[replace]`/override entries.
  3. Run `cargo update` after consolidating to refresh resolution.

Example fix

# before: both override and patch present
[replace]
'foo:1.0.0' = { path = '../foo-override' }
[patch.crates-io]
foo = { git = 'https://example.net/foo-fork' }

# after: patch only
[patch.crates-io]
foo = { git = 'https://example.net/foo-fork' }
Defensive patterns

Strategy: validation

Validate before calling

fn has_no_patch_override_conflict(has_patch: bool, has_override: bool) -> bool {
    !(has_patch && has_override)
}
// detect both a `[patch]` table and a `[replace]`/path override for the same dep

Prevention

When it happens

Trigger: The lock file or workspace config supplies a path override for a source while a `[patch]` table also redirects the same dependency. Both `override_summary` and a non-empty `patches` vector reach the arm.

Common situations: Layering `[patch]` on top of a pre-existing `[replace]`/override workflow. Partial migration from `[replace]` (deprecated) to `[patch]`. Local dev overrides colliding with CI patch tables.

Related errors


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

Appendix: source

Thrown at src/workspace/registry.rs:797

                let callback = &mut |summary: IndexSummary| {
                    for patch in patches.iter() {
                        let patch = patch.package_id().version();
                        if summary.package_id().version() == patch {
                            return;
                        }
                    }
                    let summary =
                        summary.map_summary(|summary| lock(locked, all_patches, summary, mode));
                    f(summary)
                };
                return query_with_context(&*source, dep, kind, callback).await;
            }

            // If we have an override summary then we query the source to sanity check its results.
            // We don't actually use any of the summaries it gives us though.
            (Some(override_summary), Some(source)) => {
                if !patches.is_empty() {
                    return Err(anyhow::anyhow!("found patches and a path override"));
                }
                let mut n = 0;
                let mut to_warn = None;
                let callback = &mut |summary| {
                    n += 1;
                    match summary {
                        IndexSummary::Candidate(summary)
                        | IndexSummary::Yanked(summary)
                        | IndexSummary::Offline(summary)
                        | IndexSummary::Unsupported(summary, _)
                        | IndexSummary::Invalid(summary) => {
                            to_warn = Some(summary);
                        }
                    }
                };
                query_with_context(&*source, dep, kind, callback).await?;
                if n > 1 {
                    return Err(anyhow::anyhow!("found an override with a non-locked list"));

View on GitHub (pinned to 98a09e7e7d)