astrid-runtime/astrid · error

inspect capsule materialization: {error}

Error message

inspect capsule materialization: {error}

What it means

At the start of repair_published_materialization, the kernel calls std::fs::symlink_metadata on the materialization target to decide whether the existing projection is intact, stale, or absent. NotFound is treated as 'nothing there yet', but any other stat failure (e.g. permission denied on an ancestor, IO error, too many symlinks) aborts with this message rather than risk deleting or overwriting a target whose state is unknown. The library throws it because blindly repairing a directory it cannot even stat would be unsafe.

Source

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

        snapshot: &astrid_storage::CapsulePackageSnapshot,
    ) -> anyhow::Result<astrid_capsule_types::manifest::CapsuleManifest> {
        self.repair_published_materialization(target, principal, manifest, snapshot)
    }

    /// Replace a canonical stale projection without trusting the old manifest.
    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    pub(crate) fn repair_published_materialization(
        &self,
        target: &Path,
        principal: &astrid_core::principal::PrincipalId,
        discovery_manifest: &astrid_capsule_types::manifest::CapsuleManifest,
        snapshot: &astrid_storage::CapsulePackageSnapshot,
    ) -> anyhow::Result<astrid_capsule_types::manifest::CapsuleManifest> {
        self.validate_published_cache_path(target, principal, discovery_manifest, snapshot)?;
        let target_metadata = match std::fs::symlink_metadata(target) {
            Ok(metadata) => Some(metadata),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
            Err(error) => return Err(anyhow::anyhow!("inspect capsule materialization: {error}")),
        };
        if let Some(metadata) = target_metadata {
            if metadata.file_type().is_symlink() || !metadata.is_dir() {
                anyhow::bail!("capsule materialization target is redirected or not a directory");
            }
            if let Ok(bound_manifest) =
                astrid_capsule::discovery::load_manifest(&target.join("Capsule.toml"))
                && self
                    .verify_published_materialization(target, principal, &bound_manifest, snapshot)
                    .is_ok()
            {
                return Ok(bound_manifest);
            }
            astrid_core::platform_fs::verify_no_redirects(target).map_err(|error| {
                anyhow::anyhow!("capsule materialization target is redirected: {error}")
            })?;
            std::fs::remove_dir_all(target).map_err(|error| {
                anyhow::anyhow!("remove stale capsule materialization: {error}")

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix access to the target path and its ancestors (chown/chmod so the process or the principal's UID can traverse and stat it).
  2. Check that the parent mount/filesystem is available (df/mount; remount a stale NFS cache).
  3. Verify no MAC policy (SELinux/AppArmor) is blocking access (check audit logs, adjust policy or context).
  4. If the target is genuinely broken and safe to remove, remove it manually so symlink_metadata returns NotFound and the repair path can proceed.

Example fix

// before: repair fails because the cache dir is owned by root
let m = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;

// after: restore ownership, then repair
// $ sudo chown -R <principal-uid>:<principal-gid> /path/to/cache/target
let m = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn target_stattable(target: &std::path::Path) -> Result<bool, String> {
    match std::fs::symlink_metadata(target) {
        Ok(_) => Ok(true),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
        Err(e) => Err(format!("target not inspectable: {e}")),
    }
}

Type guard

fn is_inspectable_dir(target: &std::path::Path) -> bool {
    std::fs::symlink_metadata(target)
        .map(|md| !md.file_type().is_symlink() && md.is_dir())
        .unwrap_or(false)
}

Try / catch

match kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot) {
    Ok(m) => /* proceed */,
    Err(e) if e.to_string().contains("inspect capsule materialization") => {
        // stat failed: check mount/permissions, or remove broken target so
        // repair can take the NotFound path
        let _ = std::fs::remove_dir_all(&target);
        let m = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: repair_published_materialization (called by capture_bound_materialization or ensure_published_materialization) runs while symlink_metadata(target) fails with an error other than NotFound — e.g. a parent directory is not searchable (permission denied), the path is on an unavailable/failed mount, the path exceeds filesystem limits, or an IO error occurs.

Common situations: Cache directory owned by another user/UID after running under a different account; NFS/network mount offline or stale; SELinux/AppArmor denying access to the cache path; overly restrictive permissions on an ancestor directory; a very long path or corrupted directory entry.

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