denoland/deno · error · anyhow::Error

Invalid extract destination: {}

Error message

Invalid extract destination: {}

What it means

extract_tarball_gz_atomic extracts an npm tarball into an atomic staging directory placed as a sibling of the destination. Before staging, it needs the destination's parent directory via Path::parent(); if the path has no parent (e.g. a bare relative name like "foo" with no directory component, or the filesystem root), this anyhow error is thrown because the atomic-rename strategy cannot proceed.

Source

Thrown at cli/tools/installer/npm_compat.rs:1057

      continue;
    }
    let _ = std::fs::remove_dir_all(path);
  }
}

/// Extract a gzipped npm-style tarball so that `dest` is never observable in a
/// half-extracted state: unpack into a sibling staging directory on the same
/// filesystem, then `rename` it into place.
///
/// Concurrent `deno check` invocations sharing a cache race to materialize the
/// same package. The rename makes each one either publish a complete tree or
/// lose harmlessly to a winner that already did.
fn extract_tarball_gz_atomic(
  gz_bytes: &[u8],
  dest: &Path,
) -> Result<(), AnyError> {
  let parent = dest.parent().ok_or_else(|| {
    anyhow!("Invalid extract destination: {}", dest.display())
  })?;
  std::fs::create_dir_all(parent)?;

  // Stage as a sibling so the rename stays within one filesystem. The pid plus
  // a process-local counter keeps concurrent extractions - across processes and
  // within one - from sharing a staging dir.
  static STAGING_COUNTER: std::sync::atomic::AtomicU64 =
    std::sync::atomic::AtomicU64::new(0);
  let name = dest.file_name().and_then(|n| n.to_str()).ok_or_else(|| {
    anyhow!("Invalid extract destination: {}", dest.display())
  })?;
  // The leading dot is required because this parent is passed to TypeScript as
  // a typeRoots directory, and TypeScript ignores dot-prefixed entries when it
  // enumerates type packages.
  let staging_prefix = format!(".{name}.tmp-");
  // Reclaim staging dirs left by killed processes. Keep recent dirs because
  // they may belong to another extraction currently racing with this one.
  if let Some(stale_before) = std::time::SystemTime::now()

View on GitHub (pinned to 336da420f4)

Solutions

  1. Pass a full destination path that includes a directory component, e.g. use an absolute path under a cache dir.
  2. Canonicalize or make the destination absolute (std::path::absolute / cwd.join) before calling.
  3. Check dest.parent().is_some() before invoking the install/extract API.
  4. Verify the DENO_DIR / npm cache configuration points to a real subdirectory, not a root.

Example fix

// before
let dest = Path::new("react");
extract_tarball_gz_atomic(&gz, dest)?;
// after
let dest = std::path::absolute("react")?; // ensures a parent exists
extract_tarball_gz_atomic(&gz, &dest)?;
Defensive patterns

Strategy: validation

Validate before calling

if (dest.parent().is_none()) {
  throw new Error(`extract destination needs a directory component: ${dest.display()}`);
}

Type guard

fn has_parent(p: &Path) -> bool { p.parent().is_some() }

Prevention

When it happens

Trigger: Calling download_npm_package or install_jsr_packages with a dest path that has no parent component — e.g. dest == "pkg" (relative, no slash), dest == "/", or an empty/normalized-away path. Path::parent() then returns None.

Common situations: Programmatically built paths where a join() collapsed to a bare filename; passing the current directory's files directly instead of a directory path; running from a context that strips the directory prefix; misconfigured DENO_DIR pointing at a root-level path.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of denoland/deno@336da420f4 (2026-09-11). Data as JSON: /api/errors/c3bd0757e2e92757. Report an issue: GitHub.