astrid-runtime/astrid · error

capsule materialization destination already exists

Error message

capsule materialization destination already exists

What it means

materialize_capsule_package requires a fresh destination: if symlink_metadata succeeds and the path is a real (non-symlink) directory, the extract is refused to guarantee a clean, exactly-verified materialization. This makes the function idempotence-safe — reusing a destination would mix old and new files and break receipt/hash verification.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:544

}

/// Materialize a verified durable package into a fresh disposable directory.
///
/// This helper is for loaders whose current host ABI still accepts a path. It
/// never establishes authority: callers must retain the package snapshot and
/// digest, and the destination must be a new cache generation. Archive paths,
/// links, special files, duplicate entries, and symlinked parents are rejected
/// before any bytes are written. Exact metadata and authority sidecars are
/// restored from the package bytes, not trusted from the archive.
pub fn materialize_capsule_package(
    package: &CapsulePackage,
    destination: &Path,
) -> anyhow::Result<()> {
    match fs::symlink_metadata(destination) {
        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
            bail!("capsule materialization destination is not a directory")
        },
        Ok(_) => bail!("capsule materialization destination already exists"),
        Err(error) if error.kind() == io::ErrorKind::NotFound => {},
        Err(error) => {
            return Err(error).with_context(|| format!("inspect {}", destination.display()));
        },
    }
    if let Some(parent) = destination.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("create materialization parent {}", parent.display()))?;
    }
    fs::create_dir(destination)
        .with_context(|| format!("create materialization {}", destination.display()))?;
    let decoder = flate2::read::GzDecoder::new(Cursor::new(&package.archive));
    let mut archive = tar::Archive::new(decoder);
    let mut names = std::collections::BTreeSet::new();
    for entry in archive.entries().context("read durable capsule archive")? {
        let mut entry = entry.context("read durable capsule archive entry")?;
        let path = entry.path().context("read durable capsule archive path")?;
        if path.is_absolute()

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the existing destination directory before calling (fs::remove_dir_all).
  2. Use a unique destination per run (tempdir or a versioned/timestamped path).
  3. If the existing directory should be kept, materialize into a new sibling directory instead.

Example fix

// before
let dest = Path::new("/tmp/capsule-materialize");
materialize_capsule_package(&package, dest)?;
// after
let dest = Path::new("/tmp/capsule-materialize");
if dest.exists() {
    fs::remove_dir_all(dest)?;
}
materialize_capsule_package(&package, dest)?;
Defensive patterns

Strategy: validation

Validate before calling

let dest = Path::new("/tmp/capsule-materialize");
if dest.symlink_metadata().is_ok() {
    fs::remove_dir_all(dest)?;
}
materialize_capsule_package(&package, dest)?;

Try / catch

match materialize_capsule_package(&pkg, &dest) {
    Ok(()) => {}
    Err(e) if e.to_string().contains("already exists") => {
        fs::remove_dir_all(&dest)?;
        materialize_capsule_package(&pkg, &dest)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling materialize_capsule_package twice with the same destination; running migration or tests over a destination directory left behind by a previous failed/partial run; a destination directory that already contains files.

Common situations: Re-running a migration after a crash; integration tests reusing a fixed temp dir; a deploy script that doesn't clean its staging directory between runs.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/2f4c0baa7f48ae43. Report an issue: GitHub.