astrid-runtime/astrid · error

read materialized capsule member {relative}: {error}

Error message

read materialized capsule member {relative}: {error}

What it means

After checking the projection's file and directory inventory, verify_published_materialization reads every archive member of the capsule at its materialized path with read_projection_file_nofollow; any I/O failure is wrapped as 'read materialized capsule member {relative}'. The library throws it because a published capsule is only trustworthy when every member can be read and compared byte-for-byte against the durable archive. This is an I/O wrapper — a byte difference produces the separate 'differs from durable archive' error. The offending member path is included in the message.

Source

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

            .collect::<std::collections::BTreeSet<_>>();
        let archive_directories = verified
            .archive_directories()
            .map(ToOwned::to_owned)
            .collect::<Vec<_>>();
        expected_directories.extend(archive_directories.iter().cloned());
        expected_directories.extend(
            archive_directories
                .iter()
                .map(String::as_str)
                .flat_map(authenticated_ancestor_directories),
        );
        if actual.directories != expected_directories {
            anyhow::bail!("materialized capsule directory inventory differs from durable package");
        }
        for (relative, expected) in &expected_files {
            let materialized =
                Self::read_projection_file_nofollow(&dir.join(relative)).map_err(|error| {
                    anyhow::anyhow!("read materialized capsule member {relative}: {error}")
                })?;
            if materialized != *expected {
                anyhow::bail!(
                    "materialized capsule member {relative} differs from durable archive"
                );
            }
        }
        let expansions = manifest
            .capabilities
            .expansions_from(&verified.authority().approved_capabilities);
        if !expansions.is_empty() {
            anyhow::bail!("materialized capsule manifest exceeds durable authority approval");
        }
        Ok(())
    }

    /// Bind durable activation or authorize the explicit workspace portal.
    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]

View on GitHub (pinned to affd8760f4)

Solutions

  1. Delete the affected materialization directory and re-materialize from the durable snapshot (repair_published_materialization / ensure_published_materialization).
  2. Inspect the named member in the message: ensure it exists, is a regular file, and is readable (ls -la <dir>/<relative>).
  3. Restore permissions/ownership on the cache tree to the principal's UID owner.
  4. Check for symlinked members or external interference in the cache dir and re-extract cleanly; check disk health/space if read errors persist.

Example fix

// before: failing on a cache dir missing archive members
let manifest = kernel.load_capsule(&dir, &principal, &manifest_for_discovery)?;

// after: force repair of the incomplete projection first
let snapshot = kernel.published_capsule_snapshot(&principal, &manifest_for_discovery)?;
let target = kernel.published_cache_target(&principal, &manifest_for_discovery, &snapshot)?;
let bound = kernel.repair_published_materialization(&target, &principal, &manifest_for_discovery, &snapshot)?;
Defensive patterns

Strategy: validation

Validate before calling

fn all_members_readable(dir: &std::path::Path, relatives: &[&str]) -> Result<(), String> {
    for rel in relatives {
        let p = dir.join(rel);
        let md = std::fs::symlink_metadata(&p)
            .map_err(|e| format!("{rel}: {e}"))?;
        if md.file_type().is_symlink() { return Err(format!("{rel}: symlink not allowed")); }
        std::fs::File::open(&p).map_err(|e| format!("{rel}: {e}"))?;
    }
    Ok(())
}

Type guard

fn projection_complete(dir: &std::path::Path, relatives: &[&str]) -> bool {
    relatives.iter().all(|rel| {
        std::fs::symlink_metadata(dir.join(rel))
            .map(|md| md.is_file())
            .unwrap_or(false)
    })
}

Try / catch

match kernel.load_capsule(&dir, &principal, &manifest) {
    Ok(bound) => /* proceed */,
    Err(e) if e.to_string().starts_with("read materialized capsule member ") => {
        let member = e.to_string(); // parse relative path from message if needed
        let _ = std::fs::remove_dir_all(&dir);      // rebuild torn projection
        let bound = kernel.load_capsule(&dir, &principal, &manifest)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: verify_published_materialization (directly or via repair_published_materialization, confirm_published_materialization, verify_registry_materialization) iterates expected_files and read_projection_file_nofollow fails for a member: the file is missing from the projection, is a symlink (nofollow refuses), or cannot be opened/read due to permissions or IO errors.

Common situations: A capsule cache partially deleted or truncated by cleanup tooling; files extracted without preserving expected layout; a member path replaced by a symlink (e.g. malicious or accidental); per-file permission differences after restoring a cache from a backup; read errors on a full or failing disk.

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