{"record":{"id":"e5d5951413c05e6b","repo":"astrid-runtime/astrid","slug":"capsule-materialization-target-is-redirected-err","errorCode":null,"errorMessage":"capsule materialization target is redirected: {error}","messagePattern":"capsule materialization target is redirected: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/capsule_materialization.rs","lineNumber":197,"sourceCode":"        let target_metadata = match std::fs::symlink_metadata(target) {\n            Ok(metadata) => Some(metadata),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,\n            Err(error) => return Err(anyhow::anyhow!(\"inspect capsule materialization: {error}\")),\n        };\n        if let Some(metadata) = target_metadata {\n            if metadata.file_type().is_symlink() || !metadata.is_dir() {\n                anyhow::bail!(\"capsule materialization target is redirected or not a directory\");\n            }\n            if let Ok(bound_manifest) =\n                astrid_capsule::discovery::load_manifest(&target.join(\"Capsule.toml\"))\n                && self\n                    .verify_published_materialization(target, principal, &bound_manifest, snapshot)\n                    .is_ok()\n            {\n                return Ok(bound_manifest);\n            }\n            astrid_core::platform_fs::verify_no_redirects(target).map_err(|error| {\n                anyhow::anyhow!(\"capsule materialization target is redirected: {error}\")\n            })?;\n            std::fs::remove_dir_all(target).map_err(|error| {\n                anyhow::anyhow!(\"remove stale capsule materialization: {error}\")\n            })?;\n        }\n        astrid_capsule_install::materialize_capsule_package(snapshot.package(), target)\n            .map_err(|error| anyhow::anyhow!(\"materialize durable capsule package: {error:#}\"))?;\n        let bound_manifest = astrid_capsule::discovery::load_manifest(&target.join(\"Capsule.toml\"))\n            .map_err(|error| anyhow::anyhow!(error))?;\n        self.verify_published_materialization(target, principal, &bound_manifest, snapshot)?;\n        Ok(bound_manifest)\n    }\n\n    /// Recheck the immutable publication after taking activation locks.\n    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]\n    pub(crate) fn confirm_published_materialization(\n        &self,\n        dir: &Path,","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/capsule_materialization.rs#L179-L215","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Find the symlink entries reported in the underlying `verify_no_redirects` error and replace them with real files/directories, then re-run the operation.","Stop any tooling (rsync link-dest, pnpm-style symlinking, overlay mounts) that is materializing the cache directory with symlinks instead of real content.","If the target is intentionally linked, point the materialization target at a dedicated directory path that nothing else manages."],"exampleFix":"// before (developer shared cache entries via symlinks)\n$ ln -s /shared/build/wasm pkg/out.wasm\n// after (use real files, or a fresh target)\n$ rm -rf ~/.cache/astrid/capsules/<name>-<hash>\n$ astrid capsule install <name>  // re-materializes real files","handlingStrategy":"validation","validationCode":"use std::path::Path;\n\nfn target_is_clean_real_dir(target: &Path) -> Result<(), String> {\n    let md = std::fs::symlink_metadata(target)\n        .map_err(|e| format!(\"cannot stat target {}: {e}\", target.display()))?;\n    if md.file_type().is_symlink() || !md.is_dir() {\n        return Err(\"target is a symlink or not a directory\".into());\n    }\n    // Reject any symlinked entry inside the tree before invoking the library.\n    fn scan(dir: &Path) -> Result<(), String> {\n        for entry in std::fs::read_dir(dir).map_err(|e| e.to_string())? {\n            let entry = entry.map_err(|e| e.to_string())?;\n            if entry.file_type().map_err(|e| e.to_string())?.is_symlink() {\n                return Err(format!(\"symlink inside target: {}\", entry.path().display()));\n            }\n            if entry.file_type().map_err(|e| e.to_string())?.is_dir() {\n                scan(&entry.path())?;\n            }\n        }\n        Ok(())\n    }\n    scan(target)\n}","typeGuard":"fn is_real_directory(target: &Path) -> bool {\n    std::fs::symlink_metadata(target)\n        .map(|md| md.is_dir() && !md.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Never place symlinks inside capsule cache/materialization directories; copy real files instead.","Disable symlink-preserving sync tools (rsync -a with link-dest, pnpm-style linking) for the cache path.","Treat the cache directory as library-managed: never hand-edit or link entries inside it.","Keep the materialization target on a plain local filesystem, not an overlay or network mount that introduces links."],"tags":["filesystem","symlink","cache","integrity-check"],"backgroundTag":"symlink-detected-in-path","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}