astrid-runtime/astrid · error

read materialized capsule metadata: {error:#}

Error message

read materialized capsule metadata: {error:#}

What it means

During verification of a published capsule materialization, the kernel re-reads the projected meta.json file with read_projection_file_nofollow and wraps any I/O failure with this message. The library throws it because a durable capsule is only valid when every projection file (Capsule.toml, meta.json, authority.json, and archive members) can be read byte-for-byte without following symlinks; if meta.json cannot be read, integrity cannot be proven. It is an I/O-error wrapper, not a content mismatch (mismatches produce separate 'does not match' bail errors).

Source

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

            &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(
            "Capsule.toml".to_owned(),
            verified.manifest_bytes().to_vec(),
        );
        expected_files.insert("meta.json".to_owned(), verified.metadata_bytes().to_vec());
        expected_files.insert(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Delete the stale materialization directory and let the kernel re-materialize it (e.g. call repair_published_materialization / ensure_published_materialization, which removes and republishes the projection).
  2. Check that meta.json exists and is a regular file (not a symlink) inside the materialization dir: ls -la <dir>/meta.json.
  3. Fix filesystem permissions so the process (or the principal's UID owner) can read the file.
  4. Re-install or re-publish the capsule so the durable package and projection are regenerated from the snapshot.

Example fix

// before: trusting an existing cache dir that may be incomplete
let bound = kernel.load_capsule(&dir, &principal, &manifest)?;

// after: route through repair so a broken projection is rebuilt
let snapshot = kernel.published_capsule_snapshot(&principal, &manifest)?;
let target = kernel.published_cache_target(&principal, &manifest, &snapshot)?;
let bound_manifest = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;
Defensive patterns

Strategy: validation

Validate before calling

fn meta_projection_readable(dir: &std::path::Path) -> Result<(), String> {
    let p = dir.join("meta.json");
    let md = std::fs::symlink_metadata(&p)
        .map_err(|e| format!("meta.json unreadable: {e}"))?;
    if md.file_type().is_symlink() { return Err("meta.json is a symlink".into()); }
    if !md.is_file() { return Err("meta.json is not a regular file".into()); }
    std::fs::File::open(&p).map_err(|e| format!("meta.json open failed: {e}"))?;
    Ok(())
}

Type guard

fn has_regular_file(dir: &std::path::Path, name: &str) -> bool {
    std::fs::symlink_metadata(dir.join(name))
        .map(|md| md.is_file())
        .unwrap_or(false)
}

Try / catch

match kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot) {
    Ok(m) => /* use m */,
    Err(e) if e.to_string().contains("read materialized capsule metadata") => {
        let _ = std::fs::remove_dir_all(&target); // drop broken projection
        let m = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: verify_published_materialization is called (directly or via repair_published_materialization, confirm_published_materialization, or verify_registry_materialization) while the file <materialization_dir>/meta.json is missing, unreadable, is a symlink (nofollow open refuses), or the process lacks read permission on it. Typical call sites are capsule load/prepare-runtime-replacement flows that repair or confirm the published cache.

Common situations: A partially completed or interrupted materialization left the cache directory without meta.json; an external tool or user deleted files from the capsule cache; a symlink attack or accidental symlink replaced meta.json; permissions were tightened on the cache directory; disk/IO errors while reading a network-mounted cache.

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