astrid-runtime/astrid · error
capsule materialization target is redirected or not a direct
Error message
capsule materialization target is redirected or not a directory
What it means
Before reusing or replacing an existing capsule projection, repair_published_materialization inspects the target path with symlink_metadata (following nothing). If the path exists but is a symlink or any non-directory entry, the kernel bails rather than deleting or writing through it — a redirected target could point the removal/re-materialization at arbitrary filesystem locations. Note symlink_metadata does not follow the final component, so a symlinked target is detected directly.
Source
Thrown at crates/astrid-kernel/src/capsule_materialization.rs:186
/// Replace a canonical stale projection without trusting the old manifest.
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn repair_published_materialization(
&self,
target: &Path,
principal: &astrid_core::principal::PrincipalId,
discovery_manifest: &astrid_capsule_types::manifest::CapsuleManifest,
snapshot: &astrid_storage::CapsulePackageSnapshot,
) -> anyhow::Result<astrid_capsule_types::manifest::CapsuleManifest> {
self.validate_published_cache_path(target, principal, discovery_manifest, snapshot)?;
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:#}"))?;View on GitHub (pinned to affd8760f4)
Solutions
- Remove the symlink or non-directory entry at the target path yourself (after confirming it is safe), then retry so the kernel can materialize a real directory there.
- Reconfigure the cache/state location to its real directory path instead of using a symlink (the kernel intentionally refuses to traverse redirected targets).
- If the path should be a directory, inspect what created the file/symlink (dotfile managers, previous tools) and fix that setup before re-running.
- Use an alternative target path under the workspace state dir that is a plain directory.
Example fix
// before: cache path is a symlink, kernel refuses
// ~/.astrid/capsules/my-capsule -> /mnt/bigdisk/my-capsule (symlink)
// after: remove the redirect and let the kernel own the directory
std::fs::remove_file(&runtime_dir)?; // removes the symlink itself
std::fs::create_dir_all(&runtime_dir)?;
let manifest = kernel.ensure_published_materialization(
&runtime_dir, &principal, &discovery_manifest, &snapshot,
)?; Defensive patterns
Strategy: validation
Validate before calling
// Refuse to proceed if the target is not a plain directory
let meta = std::fs::symlink_metadata(&target)?;
if meta.file_type().is_symlink() || !meta.is_dir() {
std::fs::remove_file(&target)?; // drop redirect; kernel will re-materialize
} Type guard
fn is_plain_directory(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.is_dir() && !m.file_type().is_symlink())
.unwrap_or(false)
} Try / catch
match kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot) {
Err(e) if e.to_string().contains("redirected or not a directory") => {
remove_redirect_at(&target)?;
kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)
}
other => other,
} Prevention
- Do not symlink capsule cache directories to other volumes; configure the cache root instead.
- Check dotfile/sync managers for rules that replace state directories with symlinks.
- Verify target paths with symlink_metadata before invoking repair/ensure APIs.
- Clean up stray files left by crashed runs at expected cache paths before reloading.
When it happens
Trigger: repair_published_materialization (via capture_bound_materialization or ensure_published_materialization) is called with a target that exists and std::fs::symlink_metadata reports file_type().is_symlink() == true or is_dir() == false — e.g. the cache path is a symlink to another location, or a regular file/socket was left at the expected directory path.
Common situations: A user symlinked the cache directory to a bigger disk or to dotfile-managed storage; a previous failed run left a stray file where the directory should be; container image layers replaced the directory with a symlink; the state_dir was relocated and a compat symlink was created manually.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- leftover capsule authority receipt is not a regular file: {}
- legacy capsule authority root is not a regular directory: {}
- legacy capsule authority root contains a non-regular entry:
- legacy capsule authority root is not a regular directory: {}
- directory symlink {} not allowed in capsule source tree (ref
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/7282582df00926e0.
Report an issue: GitHub.