astrid-runtime/astrid · error

signed Distro member '{}' must resolve to a prebuilt .capsul

Error message

signed Distro member '{}' must resolve to a prebuilt .capsule archive

What it means

resolve_signed_capsules requires every member of a signed Distro to resolve to a prebuilt .capsule archive. When a member's source is a relative path ('.') or absolute path ('/'), it is rejected outright because signed Distros only accept either local prebuilt archives or registry-resolvable names — path-style sources would bypass the signed-lock hash verification model.

Source

Thrown at crates/astrid-cli/src/commands/init_signed_source.rs:114

            .ok_or_else(|| {
                anyhow::anyhow!("selected capsule '{}' is not in signed lock", capsule.name)
            })?;
        let pinned_tag = signed.resolved_ref.as_deref().or(capsule.tag.as_deref());
        let archive_path = staging.join(format!("{}.capsule", capsule.name));
        if let Some(local_source) =
            resolve_local_capsule_archive(&capsule.source, bundle.manifest_path.as_deref())
                .with_context(|| format!("resolve signed capsule {}", capsule.name))?
        {
            std::fs::copy(&local_source, &archive_path).with_context(|| {
                format!(
                    "copy signed capsule {} from {}",
                    capsule.name,
                    local_source.display()
                )
            })?;
        } else {
            if capsule.source.starts_with('.') || capsule.source.starts_with('/') {
                bail!(
                    "signed Distro member '{}' must resolve to a prebuilt .capsule archive",
                    capsule.name
                );
            }
            let _ = Some(
                super::super::capsule::install::resolve_capsule_to_file(
                    &capsule.source,
                    (!capsule.version.is_empty()).then_some(capsule.version.as_str()),
                    pinned_tag,
                    Some(&capsule.name),
                    &archive_path,
                )
                .await?,
            );
        }
        let bytes = std::fs::read(&archive_path)
            .with_context(|| format!("read resolved capsule {}", capsule.name))?;
        let actual = manifest_hash(&bytes);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Build the capsule first and reference the resulting prebuilt .capsule archive (path ending in .capsule) in Distro.toml.
  2. If the member should come from the registry, remove the leading '.' or '/' so the name is resolved via resolve_capsule_to_file.
  3. Verify each member's source field in Distro.toml: only registry names or prebuilt .capsule archive paths are allowed.

Example fix

// before (Distro.toml)
[[capsule]]
name = "my-tool"
source = "./my-tool"

// after
[[capsule]]
name = "my-tool"
source = "./dist/my-tool.capsule"
Defensive patterns

Strategy: validation

Validate before calling

// pre-check Distro.toml member sources
for src in $(tomlq '.capsule[].source' Distro.toml); do
  case "$src" in
    .*|/*) [[ "$src" == *.capsule ]] || echo "INVALID signed member source: $src";;
  esac
done

Type guard

fn is_valid_signed_source(source: &str) -> bool {
    !(source.starts_with('.') || source.starts_with('/')) || source.ends_with(".capsule")
}

Try / catch

// rust
match resolve_signed_capsules(&capsules, &lock) {
    Ok(members) => install(members),
    Err(e) if e.to_string().contains("must resolve to a prebuilt .capsule archive") => {
        eprintln!("build capsules first: astrid capsule build --all");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling resolve_signed_capsules with a Distro.toml member whose `source` field begins with '.' or '/', i.e. a relative or absolute filesystem path that is not a prebuilt .capsule archive.

Common situations: Hand-writing a Distro.toml and pointing a member at a source directory (e.g. source = "./my-capsule" or "/opt/capsules/foo") instead of a built .capsule file or a registry name; forgetting to run the capsule build step that produces the archive.

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/03b5f6c7e842d19b. Report an issue: GitHub.