astrid-runtime/astrid · error
capsule archive contains duplicate path {name}
Error message
capsule archive contains duplicate path {name} What it means
canonical_archive_for_source collects archive entry names into a BTreeSet to guarantee a one-to-one mapping between paths and content. If the same normalized path (backslashes normalized to slashes) appears more than once in the archive, digesting would be ambiguous — the canonical builder cannot decide which copy wins — so it bails.
Source
Thrown at crates/astrid-capsule-install/src/source_digest.rs:54
let decoder = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(decoder);
let mut names = BTreeSet::new();
for entry in archive.entries().context("read capsule archive entries")? {
let mut entry = entry.context("read capsule archive entry")?;
let path = entry.path().context("read capsule archive path")?;
if path.is_absolute()
|| path
.components()
.any(|component| matches!(component, std::path::Component::ParentDir))
{
bail!("capsule archive contains an unsafe path {}", path.display());
}
let name = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("capsule archive path is not UTF-8"))?
.replace('\\', "/");
if !names.insert(name.clone()) {
bail!("capsule archive contains duplicate path {name}");
}
let entry_type = entry.header().entry_type();
if !entry_type.is_dir() && !entry_type.is_file() {
bail!("capsule archive contains a link or special file {name}");
}
let destination = staging.path().join(&path);
if entry_type.is_dir() {
fs::create_dir_all(&destination)
.with_context(|| format!("create capsule archive directory {name}"))?;
continue;
}
if let Some(parent) = destination.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("create capsule archive parent for {name}"))?;
}
entry
.unpack(&destination)
.with_context(|| format!("unpack capsule archive file {name}"))?;View on GitHub (pinned to affd8760f4)
Solutions
- List duplicates: `tar -tzf capsule.tgz | sort | uniq -d`.
- Recreate the archive from a clean tree instead of appending: `rm capsule.tgz && tar -czf capsule.tgz -C <root> .`.
- Fix the packaging script so it always builds a fresh archive per run.
Example fix
// before: append to existing archive, duplicating entries tar -rf capsule.tar Capsule.toml # run twice -> duplicates // after: always rebuild from scratch tar -czf capsule.tgz -C capsule-root .
Defensive patterns
Strategy: validation
Validate before calling
let out = std::process::Command::new("sh")
.arg("-c")
.arg(format!("tar -tzf {} | sort | uniq -d", archive_path))
.output()?;
if !out.stdout.is_empty() {
eprintln!("duplicate archive entries: {}", String::from_utf8_lossy(&out.stdout));
} Type guard
fn has_no_duplicates(names: &[String]) -> bool {
let mut seen = std::collections::HashSet::new();
names.iter().all(|n| seen.insert(n.replace('\\', "/")))
} Try / catch
match archive_digest_for_source(archive) {
Err(e) if e.to_string().contains("duplicate path") => {
anyhow::bail!("recreate the archive from a clean tree; do not append to an existing tar");
}
other => other,
} Prevention
- Always create archives fresh (rm + tar -czf) instead of appending with tar -rf
- Never concatenate compressed archives to merge them
- Add a CI step that fails on duplicate `tar -t` entries
- Keep packaging scripts idempotent
When it happens
Trigger: Calling archive_digest_for_source on a .tar.gz that contains two entries with the identical path, e.g. from appending to an existing archive (`tar -rf` twice) or concatenating two archives.
Common situations: Incremental repacking scripts that append instead of recreating the archive; archives merged by naive `cat a.tgz b.tgz`-style tooling; CI caches that reuse a growing archive file.
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
- durable capsule archive contains duplicate path {name}
- durable capsule archive contains duplicate directory path
- capsule archive contains an unsafe path {}
- capsule archive contains a link or special file {name}
- durable capsule archive contains unsafe path
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/cb8f1f554f6a5d9f.
Report an issue: GitHub.