astrid-runtime/astrid · error

durable capsule {id} WIT digest mismatch for {relative}

Error message

durable capsule {id} WIT digest mismatch for {relative}

What it means

Each WIT file pinned in capsule metadata carries a blake3 hex digest ('pin'). verify_wit_files hashes the actual bytes of `wit/<relative>` from the archive and compares, also requiring the pin to be a 64-char lowercase hex string. A mismatch means the archive's WIT content differs from what the metadata pins — tampering, corruption, or stale metadata — so the durable package is rejected.

Source

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

        if relative_path.is_absolute()
            || relative_path.components().any(|component| {
                matches!(
                    component,
                    std::path::Component::ParentDir | std::path::Component::RootDir
                )
            })
            || relative_path
                .components()
                .any(|component| !matches!(component, std::path::Component::Normal(_)))
        {
            bail!("durable capsule {id} has unsafe WIT metadata path {relative}");
        }
        let key = format!("wit/{relative}");
        let Some(bytes) = files.get(&key) else {
            bail!("durable capsule {id} is missing WIT file {relative}");
        };
        if !is_hex_digest(pin) || blake3::hash(bytes).to_hex().as_str() != pin {
            bail!("durable capsule {id} WIT digest mismatch for {relative}");
        }
        expected.insert(key);
    }
    for key in files.keys().filter(|key| key.starts_with("wit/")) {
        if Path::new(key)
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("wit"))
            && !expected.contains(key)
        {
            bail!("durable capsule {id} has an unpinned WIT file {key}");
        }
    }
    Ok(())
}

fn is_hex_digest(value: &str) -> bool {
    value.len() == 64
        && value

View on GitHub (pinned to affd8760f4)

Solutions

  1. Regenerate the capsule package so metadata pins are recomputed from the current .wit files, then republish/reinstall.
  2. Check the pin value is a 64-character lowercase hex blake3 digest and correct typos.
  3. If files were hand-edited inside the durable store, rebuild the capsule from source instead.
  4. Compare `blake3 hash wit/<file>` output against the metadata pin to identify which file drifted.

Example fix

// before: stale pin after editing the wit file
wit_files: {"api.wit": "<old-hash>"}
// after: regenerate pins from current content
wit_files: {"api.wit": blake3_hex(read("wit/api.wit"))}
Defensive patterns

Strategy: validation

Validate before calling

fn wit_pins_match(wit_files: &[(String, String)], files: &std::collections::BTreeMap<String, Vec<u8>>) -> bool {
    wit_files.iter().all(|(rel, pin)| {
        pin.len() == 64 && pin.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
            && files.get(&format!("wit/{rel}")).map(|bytes| blake3::hash(bytes).to_hex().as_str() == pin.as_str()).unwrap_or(false)
    })
}

Type guard

fn is_hex_digest(value: &str) -> bool {
    value.len() == 64 && value.bytes().all(|b| b.is_ascii_digit() || matches!(b, b'a'..=b'f'))
}

Try / catch

match read_verified_durable_package_for_owner(id) {
    Err(e) if e.to_string().contains("WIT digest mismatch") => {
        // content drifted from pinned metadata: rebuild and republish the capsule
        rebuild_and_republish(id)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: read_verified_durable_package_for_owner where metadata.wit_files contains an invalid pin (not 64 hex chars) or the blake3 hash of the archive's wit file bytes differs from the pin — e.g. the .wit file was modified after metadata generation, or the wrong pin was recorded.

Common situations: Editing a .wit file and re-archiving without regenerating metadata pins; copy-paste of pins between files; corrupted archive transfer; mixing files from two capsule versions.

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/ee2efb18bf105f94. Report an issue: GitHub.