rust-lang/cargo · warning
package replacement is not used
Error message
package replacement is not used: {} What it means
During workspace resolution, Cargo checks each `[replace]` entry from the workspace root against the resolved graph: if no resolved package matches the replace spec while failing the dependency's match mode, the replacement never applies. Rather than erroring, resolve_ws_with_opts emits this warning to tell the developer the replace rule is dead configuration.
Solutions
- Review the named replace spec in the workspace root Cargo.toml and correct the package name/version so it matches a package actually in the dependency graph.
- Remove the unused [replace] entry, or migrate to [patch] which is the modern mechanism for overriding sources.
- Run `cargo tree -i <spec>` (or inspect Cargo.lock) to confirm what the spec should match.
Example fix
// before: Cargo.toml
[replace]
"serde:1.0.0" = { path = "../serde" }
// after: Cargo.toml (matches resolved serde 1.0.2)
[replace]
"serde:1.0.2" = { path = "../serde" } Defensive patterns
Strategy: validation
Validate before calling
# confirm each [replace] spec actually matches a locked package
for spec in $(awk '/^"/ {print $1}' Cargo.toml | tr -d '"'); do
grep -q "${spec%%:*}" Cargo.lock || echo "replace spec '$spec' matches nothing"
done Try / catch
// the warning is non-fatal; capture shell output and flag it
if stderr.contains("package replacement is not used:") {
// treat as dead config: alert or fail CI lint step
} Prevention
- Prefer [patch] over [replace]; [replace] is legacy.
- After dependency bumps, re-check that replace/patch spec versions still resolve.
- Treat cargo warnings from resolution as CI failures to catch dead config early.
When it happens
Trigger: Calling resolve_ws_with_opts on a workspace whose root manifest declares a [replace] section (e.g. replace "with" = { path = "..." }) where the spec string matches a resolved package only via the dependency's looser match mode, or matches nothing at all — commonly because the spec's version doesn't match what actually resolved.
Common situations: Pinned replace specs whose versions drifted after a lockfile update; replaced crate names spelled differently from the resolved package; legacy [replace] usage that modern dependency resolution no longer engages; migration to [patch] leaving stale entries.
Related errors
- can't checkout from ' ': you are in the offline mode ( )
- can't find library ` `, rename file to `src/lib.rs` or…
- cannot mix `proc-macro` crate type with others
- cannot specify both `metabuild` and `build`
- config.json not found
AI-assisted analysis of rust-lang/cargo@98a09e7e7d (2026-09-22).
Data as JSON: /api/errors/ecff6b503aa6e5bc.
Report an issue: GitHub.
Appendix: source
Thrown at src/ops/resolve.rs:186
let (resolve, resolved_with_overrides) = if ws.require_optional_deps() {
// First, resolve the root_package's *listed* dependencies, as well as
// downloading and updating all remotes and such.
let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
// No need to add patches again, `resolve_with_registry` has done it.
let add_patches = false;
// Second, resolve with precisely what we're doing. Filter out
// transitive dependencies if necessary, specify features, handle
// overrides, etc.
add_overrides(&mut registry, ws)?;
for (replace_spec, dep) in ws.root_replace() {
if !resolve.iter().any(|r| {
replace_spec.matches(r) && !dep.matches_id(r, VersionReqMatchMode::Default)
}) {
ws.gctx()
.shell()
.warn(format!("package replacement is not used: {}", replace_spec))?
}
let mut unused_fields = Vec::new();
if dep.features().len() != 0 {
unused_fields.push("`features`");
}
if !dep.uses_default_features() {
unused_fields.push("`default-features`")
}
if !unused_fields.is_empty() {
ws.gctx().shell().print_report(
&[Level::WARNING
.secondary_title(format!(
"unused field in replacement for `{}`: {}",
dep.package_name(),
unused_fields.join(", ")
))
.element(Level::NOTE.message(format!(View on GitHub (pinned to 98a09e7e7d)