Problem
Running cargo add with the same dependency name given twice in one invocation (e.g. with different version requirements) prints an "Adding" status line for both, but the resulting Cargo.toml only contains the last one — with no warning, no error, and exit code 0. The status output is misleading: it claims the first entry was added, when it was actually silently overwritten before the command finished.
Reproduced with a fresh cargo new project:
$ cargo add serde@1.0.229 serde@1.0.200
Updating crates.io index
Adding serde v1.0.229 to dependencies
Features:
+ std
...
Adding serde v1.0.200 to dependencies
Features:
+ std
...
Updating crates.io index
Locking 7 packages to latest Rust 1.92.0 compatible versions
$ cat Cargo.toml
[dependencies]
serde = "1.0.200"
Root cause, verified on upstream/master (commit 7759f8c), in src/ops/cargo_add/mod.rs:
- Lines 94-115:
options.dependencies is resolved into a deps list with no dedup/conflict check on crate name.
- Line 132:
for dep in deps { ... } iterates every resolved dep unconditionally.
- Line 133:
print_action_msg(...) prints the "Adding … to dependencies" line for every entry, including ones about to be clobbered.
- Line 244:
manifest.insert_into_table(...) inserts under the dependency's TOML key, so a second entry with the same key silently overwrites the first.
Notably, cargo remove handles the analogous case differently. cargo remove serde serde fails loudly on the second occurrence ("the dependency 'serde' could not be found in 'dependencies'", exit 101), so add and remove aren't even consistent with each other here.
Steps
- cargo new demo && cd demo
- cargo add serde@1.0.229 serde@1.0.200
- cat Cargo.toml. Note only
serde = "1.0.200" is present, despite both "Adding" lines being printed, and the command exits 0
Possible Solution(s)
Detect duplicate crate names within a single cargo add invocation (in src/ops/cargo_add/mod.rs, around where options.dependencies is resolved into deps, lines 94-115) and either reject the command with an error, or at minimum emit a warning before silently keeping only the last occurrence. Rather than printing a full "Adding" success line for an entry that's about to be discarded. The exact behavior (hard error vs. warn-and-keep-last vs. merge/reconcile version reqs) is left to maintainer discretion.
Notes
Not a duplicate of #13899 (warns about duplicate versions in the resolved/transitive dependency graph, not repeated names in a single cargo add call), #16101 (conflict between dependencies and dev-dependencies sections, a different scenario), or #15624/#9599 (cargo tree display of transitive duplicates, unrelated command). #16276 fixed a similar class of silent CLI-spec-handling bug in cargo update --breaking, which is useful precedent but doesn't cover cargo add.
Version
cargo 1.92.0 (344c4567c 2025-10-21)
release: 1.92.0
commit-hash: 344c4567c634a25837e3c3476aac08af84cf9203
commit-date: 2025-10-21
host: x86_64-pc-windows-msvc
libgit2: 1.9.1 (sys:0.20.2 vendored)
libcurl: 8.15.0-DEV (sys:0.4.83+curl-8.15.0 vendored ssl:Schannel)
os: Windows 10.0.26200 (Windows 11 CoreSingleLanguage) [64-bit]
Problem
Running
cargo addwith the same dependency name given twice in one invocation (e.g. with different version requirements) prints an "Adding" status line for both, but the resultingCargo.tomlonly contains the last one — with no warning, no error, and exit code 0. The status output is misleading: it claims the first entry was added, when it was actually silently overwritten before the command finished.Reproduced with a fresh
cargo newproject:Root cause, verified on
upstream/master(commit 7759f8c), insrc/ops/cargo_add/mod.rs:options.dependenciesis resolved into adepslist with no dedup/conflict check on crate name.for dep in deps { ... }iterates every resolved dep unconditionally.print_action_msg(...)prints the "Adding … to dependencies" line for every entry, including ones about to be clobbered.manifest.insert_into_table(...)inserts under the dependency's TOML key, so a second entry with the same key silently overwrites the first.Notably,
cargo removehandles the analogous case differently.cargo remove serde serdefails loudly on the second occurrence ("the dependency 'serde' could not be found in 'dependencies'", exit 101), soaddandremovearen't even consistent with each other here.Steps
serde = "1.0.200"is present, despite both "Adding" lines being printed, and the command exits 0Possible Solution(s)
Detect duplicate crate names within a single
cargo addinvocation (insrc/ops/cargo_add/mod.rs, around whereoptions.dependenciesis resolved intodeps, lines 94-115) and either reject the command with an error, or at minimum emit a warning before silently keeping only the last occurrence. Rather than printing a full "Adding" success line for an entry that's about to be discarded. The exact behavior (hard error vs. warn-and-keep-last vs. merge/reconcile version reqs) is left to maintainer discretion.Notes
Not a duplicate of #13899 (warns about duplicate versions in the resolved/transitive dependency graph, not repeated names in a single
cargo addcall), #16101 (conflict between dependencies and dev-dependencies sections, a different scenario), or #15624/#9599 (cargo treedisplay of transitive duplicates, unrelated command). #16276 fixed a similar class of silent CLI-spec-handling bug incargo update --breaking, which is useful precedent but doesn't covercargo add.Version