astrid-runtime/astrid · error

legacy capsule component path is not relative

Error message

legacy capsule component path is not relative

What it means

canonical_legacy_archive (legacy native-capsule migration) validates each legacy component's path from its metadata and rejects paths that are not relative — i.e. containing RootDir or ParentDir components. This prevents a legacy manifest with an absolute or escaping path from writing outside the staging directory during migration.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:652

fn canonical_legacy_archive(
    home: &astrid_core::dirs::AstridHome,
    target: &Path,
    meta: &CapsuleMeta,
    manifest: &astrid_capsule::manifest::CapsuleManifest,
) -> anyhow::Result<Vec<u8>> {
    let staging = tempfile::tempdir().context("stage legacy capsule for migration")?;
    copy_legacy_tree(target, staging.path())?;
    if let Some(component) = manifest.components.first() {
        let component_path = component.path.clone();
        if component_path.is_absolute()
            || component_path.components().any(|part| {
                matches!(
                    part,
                    std::path::Component::ParentDir | std::path::Component::RootDir
                )
            })
        {
            bail!("legacy capsule component path is not relative");
        }
        let Some(hash) = meta.wasm_hash.as_deref() else {
            bail!("legacy capsule metadata has no WASM hash");
        };
        let wasm = home.bin_dir().join(format!("{hash}.wasm"));
        let destination = staging.path().join(component_path);
        if let Some(parent) = destination.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::copy(&wasm, &destination).with_context(|| {
            format!("restore content-addressed WASM blob for {}", wasm.display())
        })?;
    }
    for (relative, hash) in &meta.wit_files {
        let relative = Path::new(relative);
        if relative.is_absolute()
            || relative.components().any(|part| {
                matches!(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix the legacy capsule meta so component_path is a plain relative path (e.g. "components/foo.wasm").
  2. Re-register/re-install the legacy capsule with current tooling so metadata is regenerated correctly.
  3. Remove the broken legacy capsule and let migration skip/re-create it from the source component.

Example fix

// before (legacy meta.toml)
component_path = "/home/user/.astrid/capsules/foo.wasm"
// after
component_path = "components/foo.wasm"
Defensive patterns

Strategy: validation

Validate before calling

let p = std::path::PathBuf::from(&meta.component_path);
if p.is_absolute()
    || p.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::RootDir))
{
    anyhow::bail!("legacy component_path must be relative: {}", meta.component_path);
}

Try / catch

match migrate_native_capsules_with_report(&home) {
    Err(e) if e.to_string().contains("not relative") => {
        warn!("legacy capsule has bad component path; fixing meta and retrying");
        fix_legacy_component_paths(&home)?;
        migrate_native_capsules_with_report(&home)?;
    }
    other => other,
}

Prevention

When it happens

Trigger: Running migrate_native_capsules_with_report where a legacy capsule's meta stores component_path such as "/abs/component.wasm", "../component.wasm", or "a/../../b".

Common situations: Legacy capsules created by old tooling that recorded absolute install paths; hand-edited legacy meta files; a legacy install moved between machines so relative structure no longer holds.

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


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