astrid-runtime/astrid · error

durable capsule is missing Capsule.toml

Error message

durable capsule {id} is missing Capsule.toml

What it means

read_verified_durable_package_for_owner verifies the durable capsule's archive against its recorded checksum and then enumerates its files, requiring a top-level 'Capsule.toml' manifest entry. If the verified archive contains no Capsule.toml, the package is structurally invalid and this error is raised before any manifest parsing.

Solutions

  1. List the archive entries and confirm Capsule.toml sits at the archive root; rebuild with the package root as tar's working directory.
  2. Re-run the directory publish (publish_directory_package / capsule install) so the archive is regenerated correctly from the capsule source.
  3. Check packaging scripts/exclude patterns for rules that drop Capsule.toml.
  4. If the capsule was migrated from an older format, re-migrate with the current tool version.

Example fix

// before (from parent dir — manifest nested)
tar -cf capsule.tar ./my-capsule
// after (from package root — Capsule.toml at top level)
cd my-capsule && tar -cf ../capsule.tar .
Defensive patterns

Strategy: validation

Validate before calling

fn archive_has_root_manifest(tar_path: &std::path::Path) -> anyhow::Result<bool> {
    let f = std::fs::File::open(tar_path)?;
    let mut tar = tar::Archive::new(std::io::BufReader::new(f));
    for entry in tar.entries()? {
        let name = entry?.path()?.to_path_buf();
        if name.file_name().map(|n| n == "Capsule.toml").unwrap_or(false)
            && name.parent().map(|p| p.as_os_str().is_empty() || p == Path::new(".")).unwrap_or(true) {
            return Ok(true);
        }
    }
    Ok(false)
}

Prevention

When it happens

Trigger: Any caller (durable_contracts_pin, refresh_canonical_contracts_from_registry, durable_capsule_metadata, read_verified_durable_package, archive-entry tests, leftover matching) reads a durable capsule whose archive was built without the manifest at the archive root — e.g. the manifest ended up under a subdirectory like ./Capsule.toml or was omitted by the packaging tool.

Common situations: A hand-rolled tar built from the wrong directory so Capsule.toml is nested one level deep; packaging scripts that exclude dotfiles/manifests; archives migrated by an older tool version with a different layout.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

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

/// prefer [`read_verified_durable_package`], which takes an immutable UID.
pub fn read_verified_durable_package_for_owner(
    store: &RuntimePrincipalStore,
    owner: &StateOwner,
    id: &str,
) -> anyhow::Result<Option<VerifiedDurableCapsulePackage>> {
    let registry = store.capsules();
    let Some(snapshot) = registry.get_snapshot(owner, id)? else {
        return Ok(None);
    };
    let package = snapshot.package();
    let verification = artifact::verify_archive_bytes(&package.archive)
        .with_context(|| format!("verify durable capsule archive {id}"))?;
    let inventory = read_archive_files(&package.archive)?;
    let ArchiveInventory { files, directories } = inventory;
    let manifest_bytes = files
        .get("Capsule.toml")
        .cloned()
        .ok_or_else(|| anyhow::anyhow!("durable capsule {id} is missing Capsule.toml"))?;
    let manifest_text = String::from_utf8(manifest_bytes.clone())
        .with_context(|| format!("durable capsule {id} manifest is not UTF-8"))?;
    let manifest: CapsuleManifest = toml::from_str(&manifest_text)
        .with_context(|| format!("decode durable capsule {id} manifest"))?;
    let metadata: CapsuleMeta = serde_json::from_slice(&package.metadata)
        .with_context(|| format!("decode durable capsule {id} metadata"))?;
    let authority: InstalledAuthority = serde_json::from_slice(&package.authority)
        .with_context(|| format!("decode durable capsule {id} authority"))?;
    let metadata_bytes = package.metadata.clone();
    verify_package_identity(
        id,
        &manifest,
        &metadata,
        &authority,
        &manifest_bytes,
        &verification,
        &files,
    )?;

View on GitHub (pinned to affd8760f4)