denoland/deno · error

{hint}

Error message

{hint}

What it means

During `deno add`, `link_hint_for_spec` detects arguments that are path-shaped (., .., ./x, containing separators) and point at an existing local directory whose deno.json(c) declares a `name` — i.e. a linkable local JSR package. Adding local directories as registry deps is not supported, so Deno stops you and suggests `deno link` in the message.

Source

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

  let deps_file_fetcher = Arc::new(deps_file_fetcher);
  let jsr_resolver = Arc::new(JsrFetchResolver::new(
    deps_file_fetcher.clone(),
    cli_factory.jsr_version_resolver()?.clone(),
  ));
  let npm_resolver = Arc::new(NpmFetchResolver::new(
    deps_file_fetcher,
    npmrc.clone(),
    cli_factory.npm_version_resolver()?.clone(),
  ));

  let mut selected_packages = Vec::with_capacity(add_flags.packages.len());
  let mut package_reqs: Vec<AddRmPackageReq> =
    Vec::with_capacity(add_flags.packages.len());

  let initial_cwd = cli_factory.cli_options()?.initial_cwd().to_path_buf();
  for entry_text in add_flags.packages.iter() {
    if let Some(hint) = link_hint_for_spec(&initial_cwd, entry_text) {
      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!(

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Use `deno link ./packages/my-lib` as the message suggests (wires the local package into the workspace).
  2. If the path was accidental, fix the specifier to a real registry package (e.g. `npm:chalk`).
  3. For local-only development without linking, add a workspace member entry or a relative import instead of `deno add`.

Example fix

# before
deno add ./packages/my-lib
# after
deno link ./packages/my-lib
Defensive patterns

Strategy: validation

Validate before calling

import { statSync, readFileSync, isAbsolute, resolve } from "node:fs";
import { join } from "node:path";
const spec = process.argv[2] ?? "";
const looksPathy = spec === "." || spec === ".." || spec.includes("/") || spec.includes("\\") || isAbsolute(spec);
if (looksPathy) {
  const abs = resolve(spec);
  try {
    if (statSync(abs).isDirectory()) {
      const target = JSON.parse(readFileSync(join(abs, "deno.json"), "utf8"));
      if (target.name) {
        console.error(`${spec} is a local package; use: deno link ${spec}`);
        process.exit(1);
      }
    }
  } catch { /* not a linkable package dir */ }
}

Prevention

When it happens

Trigger: `deno add ./packages/my-lib` (or ../other, /abs/path) where that directory exists and its deno.json has a `name` field. Bare names like `deno add chalk` never trigger it; non-existent paths are passed through to normal parsing.

Common situations: Monorepos where sibling packages are consumed locally; users expecting npm-style `file:` installs; refactoring a workspace member into consumption via add.

Related errors


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