zed-industries/zed · error · anyhow::Error

destination path has no parent: {destination_path:?}

Error message

destination path has no parent: {destination_path:?}

What it means

download_server_binary needs a parent directory next to the destination to create a staging path (for extraction/verification before an atomic rename). Rust's Path::parent() returns None only for the empty path and the filesystem root '/', so this bail fires when destination_path is '' or '/'.

Source

Thrown at crates/http_client/src/github_download.rs:52

        let metadata_content = serde_json::to_string(self)
            .with_context(|| format!("serializing metadata for {metadata_path:?}"))?;
        async_fs::write(metadata_path, metadata_content.as_bytes())
            .await
            .with_context(|| format!("writing metadata file at {metadata_path:?}"))?;
        Ok(())
    }
}

pub async fn download_server_binary(
    http_client: &dyn HttpClient,
    url: &str,
    digest: Option<&str>,
    destination_path: &Path,
    asset_kind: AssetKind,
) -> Result<(), anyhow::Error> {
    log::info!("downloading github artifact from {url}");
    let Some(destination_parent) = destination_path.parent() else {
        anyhow::bail!("destination path has no parent: {destination_path:?}");
    };

    let staging_path = staging_path(destination_parent, asset_kind)?;
    let mut response = http_client
        .get(url, Default::default(), true)
        .await
        .with_context(|| format!("downloading release from {url}"))?;
    let body = response.body_mut();

    if let Err(err) = extract_to_staging(body, digest, url, &staging_path, asset_kind).await {
        cleanup_staging_path(&staging_path, asset_kind).await;
        return Err(err);
    }

    if let Err(err) = finalize_download(&staging_path, destination_path).await {
        cleanup_staging_path(&staging_path, asset_kind).await;
        return Err(err);
    }

View on GitHub (pinned to f4178619ac)

Solutions

  1. Build the destination from a guaranteed base: PathBuf::from(env::var_os("HOME").ok_or(...)?).join("...")
  2. Validate destination_path is absolute, non-empty, and not the root before calling download_server_binary
  3. Log the offending path at the call site so the misconfigured variable is obvious
  4. Check the configuration source (settings file / env) that produced the empty path

Example fix

// before
let destination_path = Path::new(&config.install_dir);
download_server_binary(&http, url, digest, destination_path, asset_kind).await?;

// after
let base = dirs::data_dir().context("no data dir")?;
let destination_path = base.join("server").join(binary_file_name);
assert!(destination_path.parent().is_some(), "invalid destination");
download_server_binary(&http, url, digest, &destination_path, asset_kind).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_destination(path: &Path) -> bool {
    !path.as_os_str().is_empty() && path.parent().is_some() && path != Path::new("/")
}
assert!(valid_destination(&destination_path));

Type guard

fn is_concrete_file_path(p: &Path) -> bool {
    p.parent().is_some() // false for "" and "/"
}

Prevention

When it happens

Trigger: Passing destination_path = Path::new("") or Path::new("/") — e.g. a joined path where every component was empty because a config dir resolved to None and was silently stringified, or an install root mistakenly given as the destination itself.

Common situations: HOME/environment variables unset so paths::data_dir()-style lookups produce empty strings; misconfigured install prefixes set to '/'; test code passing a bare filename placeholder instead of a full path.

Related errors


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