astrid-runtime/astrid · error

capsule archive contains duplicate entry '{path}'

Error message

capsule archive contains duplicate entry '{path}'

What it means

After identity checks, verify_release_manifest requires the manifest's version, tag, source_commit and release_workflow_identity to equal the corresponding fields of the signed channel pointer. This ensures the manifest actually describes the exact release the signed pointer attests to. A mismatch means the manifest and pointer are from different releases or one of them was tampered with or stale.

Source

Thrown at crates/astrid-build/src/artifact.rs:260

    read_archive_reader(GzDecoder::new(file))
}

fn read_archive_reader<R: Read>(
    reader: R,
) -> anyhow::Result<(Vec<ContentRecord>, Option<Vec<u8>>)> {
    let mut archive = tar::Archive::new(reader);
    let mut records = Vec::new();
    let mut envelope = None;
    let mut seen = HashSet::new();

    for entry in archive
        .entries()
        .context("failed to read capsule archive")?
    {
        let mut entry = entry.context("failed to read capsule archive entry")?;
        let path = normalized_entry_path(&entry)?;
        if !seen.insert(path.clone()) {
            bail!("capsule archive contains duplicate entry '{path}'");
        }
        let kind = entry.header().entry_type();
        if kind.is_dir() {
            continue;
        }
        if !kind.is_file() {
            bail!("capsule archive contains unsupported entry '{path}'");
        }
        if path == PROVENANCE_FILE {
            if entry.size() > 64 * 1024 {
                bail!("capsule provenance envelope exceeds 64 KiB");
            }
            let mut bytes = Vec::new();
            entry
                .read_to_end(&mut bytes)
                .context("failed to read capsule provenance")?;
            envelope = Some(bytes);
            continue;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-fetch both the channel pointer and the manifest from the authoritative source and retry (rules out stale caches).
  2. If the release was rebuilt, regenerate both the manifest and the signed pointer from the same build so version/tag/source_commit/workflow_identity agree.
  3. Fix the publish pipeline to update the pointer atomically with the manifest.

Example fix

# before: pointer for 1.2.3, manifest says
version = "1.2.2"
# after
version = "1.2.3"
tag = "v1.2.3"
source_commit = "<same as pointer.release.source_commit>"
Defensive patterns

Strategy: retry

Validate before calling

fn manifest_matches_pointer(m: &ReleaseManifest, p: &ChannelPointer) -> bool {
    m.version == p.release.version && m.tag == p.release.tag
        && m.source_commit == p.release.source_commit
        && m.release_workflow_identity == p.release.release_workflow_identity
}

Try / catch

match verify_release_manifest(&bytes, &pointer) {
    Err(e) if e.to_string().contains("does not match the signed channel pointer") => {
        // refresh both artifacts from origin once, then retry
    },
    other => other,
}

Prevention

When it happens

Trigger: verify_release_manifest is called with a parsed ReleaseManifest where any of manifest.version, manifest.tag, manifest.source_commit, or manifest.release_workflow_identity differs from pointer.release's matching field.

Common situations: Cache/CDN serving a manifest for version N-1 while the pointer is for N; manifest regenerated (new commit) without re-issuing the signed pointer; a pointer replayed after a re-release of the same version; partial rollback where pointer and manifest updated at different times.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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