astrid-runtime/astrid · error

capsule materialization target is redirected: {error}

Error message

capsule materialization target is redirected: {error}

What it means

This error is thrown by `repair_published_materialization` when, before wiping a stale capsule materialization directory, `astrid_core::platform_fs::verify_no_redirects(target)` finds symlinks or other redirect entries inside the target tree. The kernel refuses to `remove_dir_all` a directory whose contents are redirected (e.g. contain symlinks), because destroying it could follow links and damage files outside the cache directory. It is a defensive integrity check on the durable cache path before deletion.

Source

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

        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}")
            })?;
        }
        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,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the target directory manually (after inspecting it for symlinks pointing outside the cache) so the next run starts from a clean, redirect-free directory.
  2. Find the symlink entries reported in the underlying `verify_no_redirects` error and replace them with real files/directories, then re-run the operation.
  3. Stop any tooling (rsync link-dest, pnpm-style symlinking, overlay mounts) that is materializing the cache directory with symlinks instead of real content.
  4. If the target is intentionally linked, point the materialization target at a dedicated directory path that nothing else manages.

Example fix

// before (developer shared cache entries via symlinks)
$ ln -s /shared/build/wasm pkg/out.wasm
// after (use real files, or a fresh target)
$ rm -rf ~/.cache/astrid/capsules/<name>-<hash>
$ astrid capsule install <name>  // re-materializes real files
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;

fn target_is_clean_real_dir(target: &Path) -> Result<(), String> {
    let md = std::fs::symlink_metadata(target)
        .map_err(|e| format!("cannot stat target {}: {e}", target.display()))?;
    if md.file_type().is_symlink() || !md.is_dir() {
        return Err("target is a symlink or not a directory".into());
    }
    // Reject any symlinked entry inside the tree before invoking the library.
    fn scan(dir: &Path) -> Result<(), String> {
        for entry in std::fs::read_dir(dir).map_err(|e| e.to_string())? {
            let entry = entry.map_err(|e| e.to_string())?;
            if entry.file_type().map_err(|e| e.to_string())?.is_symlink() {
                return Err(format!("symlink inside target: {}", entry.path().display()));
            }
            if entry.file_type().map_err(|e| e.to_string())?.is_dir() {
                scan(&entry.path())?;
            }
        }
        Ok(())
    }
    scan(target)
}

Type guard

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

Prevention

When it happens

Trigger: Calling `ensure_published_materialization` or `capture_bound_materialization` when the existing target directory fails `verify_published_materialization` (stale manifest/content) AND `verify_no_redirects` then detects a symlink or redirect entry inside the target directory tree.

Common situations: A developer or tool manually symlinked files inside the capsule cache directory to elsewhere (e.g. to shared build artifacts); a cache directory was partially replaced with links by a custom sync tool; a cache-shared volume or overlayfs setup introduces symlinked entries; a previous buggy version of the materializer wrote symlinks.

Related errors


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