zed-industries/zed · error

node at {} is too old. want: {}, got: {}

Error message

node at {} is too old. want: {}, got: {}

What it means

After `node --version` succeeds, SystemNodeRuntime parses the version and enforces a floor of 22.0.0. Older system Nodes bail with the wanted and found versions. The check exists because Zed's bundled extensions/tooling require modern Node APIs; the runtime then falls back to using the data-dir layout for caches.

Source

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

impl SystemNodeRuntime {
    const MIN_VERSION: semver::Version = Version::new(22, 0, 0);
    async fn new(node: PathBuf, npm: PathBuf) -> Result<Self> {
        let output = util::command::new_command(&node)
            .arg("--version")
            .output()
            .await
            .with_context(|| format!("running node from {:?}", node))?;
        if !output.status.success() {
            anyhow::bail!(
                "failed to run node --version. stdout: {}, stderr: {}",
                String::from_utf8_lossy(&output.stdout),
                String::from_utf8_lossy(&output.stderr),
            );
        }
        let version_str = String::from_utf8_lossy(&output.stdout);
        let version = semver::Version::parse(version_str.trim().trim_start_matches('v'))?;
        if version < Self::MIN_VERSION {
            anyhow::bail!(
                "node at {} is too old. want: {}, got: {}",
                node.to_string_lossy(),
                Self::MIN_VERSION,
                version
            )
        }

        let scratch_dir = paths::data_dir().join("node");
        fs::create_dir(&scratch_dir).await.ok();
        _ = fs::remove_dir_all(scratch_dir.join("cache")).await;
        fs::create_dir(scratch_dir.join("cache")).await.ok();

        Ok(Self {
            node,
            npm,
            scratch_dir,
        })
    }

View on GitHub (pinned to 9d272b0363)

Solutions

  1. Upgrade system Node to >=22 (e.g. `nvm install 22 && nvm alias default 22`, or install from nodejs.org)
  2. Prepend the new Node's bin directory to PATH so Zed resolves it first
  3. Or remove/renumber the old node from PATH so Zed's bundled downloaded runtime is used instead

Example fix

# before
$ node --version
v18.20.4

# after
$ nvm install 22 && nvm alias default 22
$ node --version
v22.x.y
Defensive patterns

Strategy: validation

Validate before calling

fn node_new_enough(version_output: &str) -> bool {
    version_output
        .trim()
        .trim_start_matches('v')
        .parse::<semver::Version>()
        .map_or(false, |v| v >= semver::Version::new(22, 0, 0))
}

Type guard

fn meets_min_node_version(v: &semver::Version) -> bool {
    *v >= semver::Version::new(22, 0, 0)
}

Try / catch

if version < SystemNodeRuntime::MIN_VERSION {
    // report and skip the system runtime; use the bundled one instead
    return NodeRuntime::install_if_needed(&http).await;
}

Prevention

When it happens

Trigger: A system node older than v22 (18.x LTS, 20.x, ancient 16.x from distro packages) is picked up when Zed prefers the system runtime.

Common situations: Distros shipping node 18/20 as `nodejs`; stale nvm default (`nvm alias default 18`); CI images pinned to node:20; WSL environments with an old apt-installed Node.

Related errors


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