astrid-runtime/astrid · error

read materialized capsule manifest: {error:#}

Error message

read materialized capsule manifest: {error:#}

What it means

Verification reads the materialized Capsule.toml from the capsule directory without following symlinks; this I/O or policy failure is wrapped with the "read materialized capsule manifest:" prefix. Even if the durable registry record exists, the on-disk manifest must be readable to compare bytes.

Source

Thrown at crates/astrid-kernel/src/capsule_materialization.rs:39

            .map_err(|error| anyhow::anyhow!("resolve capsule cache owner UID: {error}"))?;
        let verified = astrid_capsule_install::read_verified_durable_package_for_owner(
            self.principal_store
                .as_ref()
                .ok_or_else(|| anyhow::anyhow!("durable capsule registry is unavailable"))?,
            &astrid_storage::StateOwner::Principal(uid),
            manifest.package.name.as_str(),
        )?
        .ok_or_else(|| anyhow::anyhow!("materialized capsule is absent from durable registry"))?;
        if verified.snapshot() != snapshot {
            anyhow::bail!("materialized capsule snapshot differs from the caller's publication");
        }
        if verified.manifest().package.name != manifest.package.name
            || verified.manifest().package.version != manifest.package.version
        {
            anyhow::bail!("materialized capsule manifest differs from durable registry");
        }
        let manifest_bytes = Self::read_projection_file_nofollow(&dir.join("Capsule.toml"))
            .map_err(|error| anyhow::anyhow!("read materialized capsule manifest: {error:#}"))?;
        if manifest_bytes != verified.manifest_bytes() {
            anyhow::bail!("durable capsule manifest bytes do not match materialization");
        }
        let metadata_bytes = Self::read_projection_file_nofollow(&dir.join("meta.json"))
            .map_err(|error| anyhow::anyhow!("read materialized capsule metadata: {error:#}"))?;
        if metadata_bytes != verified.metadata_bytes() {
            anyhow::bail!("durable capsule metadata does not match materialization");
        }
        let authority_bytes = Self::read_projection_file_nofollow(&dir.join("authority.json"))
            .map_err(|error| anyhow::anyhow!("read materialized capsule authority: {error:#}"))?;
        if authority_bytes != verified.snapshot().package().authority {
            anyhow::bail!("durable capsule authority bytes do not match materialization");
        }
        let mut expected_files = verified
            .archive_entries()
            .map(|(path, bytes)| (path.to_owned(), bytes.to_vec()))
            .collect::<std::collections::BTreeMap<_, _>>();
        expected_files.insert(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read the wrapped error to distinguish missing file vs symlink rejection vs permissions
  2. Ensure Capsule.toml in the projection directory is a regular file owned by the resolved UID
  3. Re-materialize the capsule (repair/confirm flow) to restore a valid manifest file
  4. Fix directory permissions or the storage volume if the read failed at the OS level
Defensive patterns

Strategy: try-catch

Validate before calling

let meta = fs.symlink_metadata(dir.join("Capsule.toml"))?;
if !meta.is_file() {
    return Err("Capsule.toml in materialization is not a regular file");
}

Type guard

fn is_regular_file(path: &Path) -> bool {
    std::fs::symlink_metadata(path).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

match read_projection_file_nofollow(&dir.join("Capsule.toml")) {
    Ok(bytes) => verify_manifest_bytes(bytes),
    Err(e) => {
        log::error!("materialized Capsule.toml unreadable (missing/symlink/perms): {e:#}");
        re_materialize(dir)
    }
}

Prevention

When it happens

Trigger: Self::read_projection_file_nofollow(&dir.join("Capsule.toml")) inside verify_published_materialization returns Err (file missing, permission problem, or the no-follow policy rejecting a symlinked Capsule.toml), and the error is mapped into this message.

Common situations: Capsule.toml was replaced by a symlink (tampering or bad tooling) and nofollow read rejects it; the cache directory was partially deleted or has wrong ownership/permissions after UID changes; disk errors on the cache volume.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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