astrid-runtime/astrid · error
remove stale capsule materialization
Error message
remove stale capsule materialization: {error} What it means
Thrown by `repair_published_materialization` when the stale materialization directory passed the redirect check but `std::fs::remove_dir_all(target)` fails. The library needs to delete the stale cache directory before re-materializing from the package snapshot; if the OS refuses the delete (permissions, busy/open files, read-only filesystem), repair cannot proceed and the underlying io::Error is wrapped verbatim.
Solutions
- Delete the target directory manually with sufficient privileges (e.g. `sudo rm -rf <target>` or fixing ownership with `chown -R`), then re-run the operation.
- Close processes holding files open inside the target directory (dev servers, watchers, containers) and retry.
- If the cache is on a read-only mount, remount it read/write or relocate the cache directory to writable storage.
- Check and clear immutable attributes (`chattr -i`) or ACLs on the cache entries.
Defensive patterns
Strategy: validation
Validate before calling
use std::path::Path;
fn target_is_deletable(target: &Path) -> Result<(), String> {
let md = std::fs::symlink_metadata(target)
.map_err(|e| format!("target missing/unreadable: {e}"))?;
if !md.is_dir() || md.file_type().is_symlink() {
return Err("target is not a real directory".into());
}
// Probe write access to the directory (a directory can be removed only
// if its parent is writable, and emptied only if children are writable).
let probe = target.join(".astrid-delete-probe");
std::fs::File::create(&probe)
.and_then(|_| std::fs::remove_file(&probe))
.map_err(|e| format!("cache dir not writable by current user: {e}"))
} Type guard
fn can_write_directory(dir: &Path) -> bool {
let probe = dir.join(".write-probe");
let ok = std::fs::File::create(&probe).is_ok();
let _ = std::fs::remove_file(&probe);
ok
} Try / catch
match ensure_published_materialization(&target, &principal, &manifest, &snapshot) {
Ok(manifest) => {/* use bound manifest */}
Err(e) if e.to_string().contains("remove stale capsule materialization") => {
eprintln!("cache dir could not be deleted: {e:#}");
eprintln!("hint: check ownership/permissions, close processes using it, or run: rm -rf {}");
}
Err(e) => return Err(e),
} Prevention
- Run all operations against the cache with the same user that created it; avoid mixing sudo and non-sudo runs.
- Point the cache at a writable local volume; never mount it read-only.
- Stop watchers/dev servers/containers that hold files open in the cache before repairing.
- Avoid immutable flags (chattr +i) or restrictive ACLs on cache contents.
When it happens
Trigger: Calling `ensure_published_materialization`/`capture_bound_materialization` where the target exists, is a real directory with a stale/unverifiable manifest, contains no redirects, but `remove_dir_all` returns an io::Error (e.g. EACCES, EBUSY, EROFS, EPERM on immutable files).
Common situations: Running the tool as a non-root user while the cache directory is owned by root (e.g. created by a sudo install); another process (editor, dev server, container) holds files open inside the cache on Windows/NFS; the cache lives on a read-only mount or Docker volume mounted `:ro`; file immutability flags (chattr +i) set on cache entries.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- cache changed to a redirect or special entry
- capsule materialization target is redirected
- capsules are installed, but granting capsule access failed
- failed to stage
- inspect legacy revocation file
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/697c5e25b4415762.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/capsule_materialization.rs:200
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}")
})?;
}
astrid_capsule_install::materialize_capsule_package(snapshot.package(), target)
.map_err(|error| anyhow::anyhow!("materialize durable capsule package: {error:#}"))?;
let bound_manifest = astrid_capsule::discovery::load_manifest(&target.join("Capsule.toml"))
.map_err(|error| anyhow::anyhow!(error))?;
self.verify_published_materialization(target, principal, &bound_manifest, snapshot)?;
Ok(bound_manifest)
}
/// Recheck the immutable publication after taking activation locks.
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn confirm_published_materialization(
&self,
dir: &Path,
principal: &astrid_core::principal::PrincipalId,
manifest: &astrid_capsule_types::manifest::CapsuleManifest,
snapshot: &astrid_storage::CapsulePackageSnapshot,View on GitHub (pinned to affd8760f4)