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
- Delete the affected materialization directory and re-materialize from the durable snapshot (repair_published_materialization / ensure_published_materialization).
- Inspect the named member in the message: ensure it exists, is a regular file, and is readable (ls -la <dir>/<relative>).
- Restore permissions/ownership on the cache tree to the principal's UID owner.
- 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
- Always materialize via the library (materialize_capsule_package / repair paths) instead of copying files manually.
- Prefer repair_published_materialization over ad-hoc cache surgery — it rebuilds torn projections from the durable snapshot.
- Check disk space and health; partial extractions commonly stem from ENOSPC.
- Avoid tools that convert cache files to symlinks (dedup/COW dedupers) — nofollow reads will refuse them.
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
- read materialized capsule metadata: {error:#}
- read materialized capsule authority: {error:#}
- inspect capsule materialization: {error}
- capsule materialization destination is not a directory
- capsule materialization destination already exists
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/c13455cba18a474e.
Report an issue: GitHub.