denoland/deno · error

{} and {} would both be added as "{}". Provide an explicit a

Error message

{} and {} would both be added as "{}". Provide an explicit alias for one of them (ex. `{}`).

What it means

With `--unscoped`, `deno add` strips scopes from names so `@a/cli` and `@b/cli` both become the import alias `cli`. Before inserting, it checks for an existing req with the same alias and bails, showing both package names and a concrete aliased command, because otherwise the second add would silently overwrite the first in deno.json.

Source

Thrown at cli/tools/pm/mod.rs:633

      bail!("{hint}");
    }
    let req = AddRmPackageReq::parse(
      entry_text,
      add_flags.default_registry.map(|r| r.into()),
    )
    .with_context(|| format!("Failed to parse package: {}", entry_text))?;

    match req {
      Ok(mut add_req) => {
        if add_flags.unscoped {
          add_req.use_unscoped_alias();
          // packages from different scopes can share an unscoped name
          // (ex. `@luca/flag` and `@other/flag`), which would otherwise
          // silently overwrite each other in the config
          if let Some(existing) =
            package_reqs.iter().find(|r| r.alias == add_req.alias)
          {
            bail!(
              "{} and {} would both be added as \"{}\". Provide an explicit alias for one of them (ex. `{}`).",
              existing.package_name(),
              add_req.package_name(),
              add_req.alias,
              crate::colors::yellow(format!(
                "deno {cmd_name} my-alias@{}",
                add_req.package_name()
              )),
            );
          }
        }
        package_reqs.push(add_req)
      }
      // Currently unreachable: default_registry is always Some (defaults to Npm),
      // so parse() always resolves a prefix. Kept as a safety fallback in case
      // the API is called with None from elsewhere.
      Err(package_req) => {
        if jsr_resolver

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Alias one of them explicitly: `deno add my-cli@npm:@b/cli` (pattern shown in the error message).
  2. Drop `--unscoped` so each keeps its scoped key `@a/cli`.
  3. Split into two invocations if both are needed unscoped, aliasing at least one.

Example fix

# before
deno add --unscoped @a/cli @b/cli
# after
deno add --unscoped @a/cli my-cli@npm:@b/cli
Defensive patterns

Strategy: validation

Validate before calling

const specs = process.argv.slice(2); // after --unscoped
const unscoped = (s: string) => s.replace(/^@[^/]+\//, "").split("@")[0];
const seen = new Map<string, string>();
for (const s of specs) {
  const alias = unscoped(s);
  if (seen.has(alias)) {
    console.error(`${seen.get(alias)} and ${s} collide on unscoped alias "${alias}"; alias one explicitly`);
    process.exit(1);
  }
  seen.set(alias, s);
}

Prevention

When it happens

Trigger: `deno add --unscoped @a/cli @b/cli` (two packages whose names after the scope are identical), or a second `--unscoped` add colliding with an existing dependency in the same invocation's list.

Common situations: Installing multiple scoped CLIs or libs with the same base name (e.g. @std/cli and @foo/cli); scripts that loop `deno add --unscoped` over package lists.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/69d77f62538c2f0a. Report an issue: GitHub.