astrid-runtime/astrid · error

durable capsule archive contains duplicate path

Error message

durable capsule archive contains duplicate path

What it means

The extractor tracks every entry path in a set and rejects the archive if the same relative path appears more than once. Duplicate entries would make the final on-disk content depend on entry order, breaking the exact-bytes verification guarantees of materialization.

Solutions

  1. Rebuild the archive with canonical_capsule_archive, which produces one entry per path.
  2. Fix the packer/CI step so it creates a fresh archive rather than appending to an existing one.
  3. Re-download the capsule archive if it came from an external source (may be corrupted or tampered).

Example fix

// before
// tar -cvf capsule.tar manifest.toml ...
// tar -rvf capsule.tar manifest.toml   # duplicate!
// after
fs::remove_file("capsule.tar").ok();
// tar -cvf capsule.tar manifest.toml ...  (single pass)
Defensive patterns

Strategy: validation

Validate before calling

let mut names = std::collections::HashSet::new();
for entry in tar::Archive::new(&bytes[..]).entries()? {
    let p = entry?.path()?.to_path_buf();
    if !names.insert(p.clone()) {
        anyhow::bail!("duplicate archive entry: {}", p.display());
    }
}

Try / catch

match materialize_capsule_package(&pkg, &dest) {
    Err(e) if e.to_string().contains("duplicate path") => {
        error!("archive malformed (duplicate entries); rebuild it");
        rebuild_canonical_archive(&src_dir)?;
    }
    other => other,
}

Prevention

When it happens

Trigger: Materializing a tar archive that contains two entries with the same relative path — e.g. an archive appended to twice, or built by concatenating two capsule archives.

Common situations: A packer bug that adds a file twice; tar --append onto an existing archive; concatenating archives during a botched migration; a tampered archive with an injected duplicate entry.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

    }
    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()
            || path
                .components()
                .any(|component| matches!(component, std::path::Component::ParentDir))
        {
            bail!("durable capsule archive contains unsafe path");
        }
        let relative = path.to_path_buf();
        if !names.insert(relative.clone()) {
            bail!("durable capsule archive contains duplicate path");
        }
        let output = destination.join(&relative);
        if entry.header().entry_type().is_symlink()
            || entry.header().entry_type().is_hard_link()
            || entry.header().entry_type().is_block_special()
            || entry.header().entry_type().is_character_special()
            || entry.header().entry_type().is_fifo()
        {
            bail!("durable capsule archive contains a link or special file");
        }
        if entry.header().entry_type().is_dir() {
            fs::create_dir_all(&output)
                .with_context(|| format!("create materialized directory {}", output.display()))?;
            continue;
        }
        if !entry.header().entry_type().is_file() {
            bail!("durable capsule archive contains an unsupported entry");
        }

View on GitHub (pinned to affd8760f4)