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
- Rebuild the archive with canonical_capsule_archive, which produces one entry per path.
- Fix the packer/CI step so it creates a fresh archive rather than appending to an existing one.
- 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
- Create archives in one pass; never tar --append onto an existing capsule archive.
- Use canonical_capsule_archive for packing so duplicates are impossible.
- Verify archive bytes before materializing; duplicates indicate corruption or tampering.
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
- capsule source changed while archiving
- durable capsule archive contains an unsupported entry
- an incomplete capsule authority update exists at
- astrid-build failed with exit code
- astrid-build produced no .capsule archive.
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)