astrid-runtime/astrid · error

capsule materialization cache is redirected or not a directo

Error message

capsule materialization cache is redirected or not a directory: {}

What it means

clear_capsule_materialization_cache refuses to wipe the materialization cache root if symlink_metadata shows it is a symlink or not a directory. This prevents a redirected cache root (an attacker- or misconfiguration-planted symlink) from causing deletion of an arbitrary directory. NotFound is tolerated (nothing to clean); any other inspection failure is wrapped in a context error.

Source

Thrown at crates/astrid-capsule-install/src/paths.rs:183

    Ok(())
}

/// Remove every disposable user capsule materialization from the runtime
/// cache after validating the complete tree without following redirects.
///
/// Durable packages are never touched. A fresh materialization is created
/// from a verified storage snapshot when needed, so deleting stale or
/// interrupted cache generations at boot is safe and avoids reusing an
/// unverified crash residue.
pub fn clear_capsule_materialization_cache(home: &AstridHome) -> anyhow::Result<()> {
    let root = home.run_dir().join("capsules");
    let metadata = match std::fs::symlink_metadata(&root) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
        Err(error) => return Err(error).context("inspect capsule materialization cache"),
    };
    if metadata.file_type().is_symlink() || !metadata.is_dir() {
        anyhow::bail!(
            "capsule materialization cache is redirected or not a directory: {}",
            root.display()
        );
    }
    validate_cache_tree(&root)?;
    for entry in std::fs::read_dir(&root).context("read capsule materialization cache")? {
        let path = entry
            .context("read capsule materialization cache entry")?
            .path();
        remove_cache_tree(&path)?;
    }
    Ok(())
}

fn validate_cache_tree(path: &Path) -> anyhow::Result<()> {
    astrid_core::platform_fs::verify_no_redirects(path)
        .with_context(|| format!("verify cache path {}", path.display()))?;
    for entry in std::fs::read_dir(path).with_context(|| format!("read {}", path.display()))? {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlink or file at the cache root path, then re-run the cleanup so the library can recreate a real directory
  2. Fix whatever creates the cache root so it is always a real directory owned by the runtime
  3. Point the cache root configuration at the intended directory rather than a link
  4. If redirecting the cache is a legitimate need, relocate the configured root itself instead of symlinking

Example fix

// before
$ ln -s /mnt/bigdisk/capsule-cache ~/.cache/astrid/capsules
// after
$ rm ~/.cache/astrid/capsules
$ mkdir -p ~/.cache/astrid/capsules   # or reconfigure the cache root to /mnt/bigdisk/capsule-cache
Defensive patterns

Strategy: validation

Validate before calling

fn cache_root_safe(root: &Path) -> anyhow::Result<bool> {
    match std::fs::symlink_metadata(root) {
        Ok(m) => Ok(!m.file_type().is_symlink() && m.is_dir()),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
        Err(e) => Err(e.into()),
    }
}

Type guard

fn is_real_directory(root: &Path) -> bool {
    std::fs::symlink_metadata(root).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)
}

Try / catch

if let Err(e) = clear_capsule_materialization_cache(&root) {
    if e.to_string().contains("redirected or not a directory") {
        // remove the symlink/file at root, recreate a real dir, retry once
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling clear_capsule_materialization_cache when the cache root path is a symlink (e.g. a user linked it to another location), or a regular file was created at the cache root path, or a previous misconfiguration pointed the cache at a non-directory path.

Common situations: Users symlinking the cache to a bigger disk or shared location; leftover file occupying the cache path after a failed install; container images where the cache path is a mounted file; tests (like cache_cleanup_removes_crash_residue) that pre-create the path incorrectly.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/7ff2454c30c621a4. Report an issue: GitHub.