zed-industries/zed · error

npm package {name} declares no executable

Error message

npm package {name} declares no executable

What it means

When resolving a package's executable, NodeRuntime reads the npm package's package.json `bin` field. The `Bin` enum distinguishes a single string bin, a named map (`Bin::Named`), and no bin at all (`None`). This message is the None case: the installed package literally declares no executable, so no binary path can be returned.

Source

Thrown at crates/node_runtime/src/node_runtime.rs:1061

    file.read_to_string(&mut contents).await?;
    let package_json: PackageJson = serde_json::from_str(&contents)
        .with_context(|| format!("parsing {}", package_json_path.display()))?;

    let relative_path = match package_json.bin {
        Some(Bin::Path(path)) => path,
        Some(Bin::Named(bins)) => {
            let unscoped_name = name.rsplit('/').next().unwrap_or(name);
            let path = if bins.len() == 1 {
                bins.values().next()
            } else {
                bins.get(unscoped_name)
            };
            path.with_context(|| {
                format!("npm package {name} declares no executable named {unscoped_name}")
            })?
            .clone()
        }
        None => bail!("npm package {name} declares no executable"),
    };

    Ok(package_directory.join(relative_path))
}

#[derive(Clone)]
pub struct UnavailableNodeRuntime {
    error_message: Arc<String>,
}

#[async_trait::async_trait]
impl NodeRuntimeTrait for UnavailableNodeRuntime {
    fn boxed_clone(&self) -> Box<dyn NodeRuntimeTrait> {
        Box::new(self.clone())
    }
    fn binary_path(&self) -> Result<PathBuf> {
        bail!("{}", self.error_message)
    }

View on GitHub (pinned to 9d272b0363)

Solutions

  1. Verify the package actually ships a CLI: `npm view <name> bin` — if empty, you want a different package
  2. Request the correct CLI package name for the tool
  3. Pin a version known to declare the bin if a release dropped it

Example fix

# before: package declares no bin
$ npm view some-parser bin
{}

# after: request the package that ships the CLI
$ npm view some-parser-cli bin
{ 'some-parser-cli': './bin/cli.js' }
Defensive patterns

Strategy: validation

Validate before calling

async fn package_has_bin(name: &str, version: &str, http: &Arc<dyn HttpClient>) -> bool {
    let meta = fetch_package_json(name, version, http).await;
    meta.get("bin").map_or(false, |b| !b.is_null())
}

Type guard

fn declares_executable(package_json: &serde_json::Value) -> bool {
    package_json.get("bin").is_some_and(|b| {
        b.is_string() || b.as_object().is_some_and(|m| !m.is_empty())
    })
}

Try / catch

match resolve_executable_path(&package) {
    Err(e) if e.to_string().contains("declares no executable") => {
        // pick the CLI package variant or pin a version that shipped a bin
        pick_cli_package_alternative(&package).await
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling the package-install/exec path (e.g. installing a language server or tool) for an npm package whose package.json has no `bin` field — typically a pure library package, or the wrong package was requested.

Common situations: Asking for the library package instead of the CLI package (e.g. a `-lib`/core package vs the bin-publishing one); a package version that temporarily dropped its bin declaration; typos in package names resolving to a library.

Related errors


AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-08-20). Data as JSON: /api/errors/1b53ab0640808987. Report an issue: GitHub.