astrid-runtime/astrid · error

distro must contain at least one capsule

Error message

distro must contain at least one capsule

What it means

A distro is, by definition, a bundle of one or more released capsules, so validate_manifest bails when `manifest.capsules` is empty. This prevents emitting a degenerate distro artifact that would install nothing. It fires after the astrid-version and requires checks in the same validation pass.

Source

Thrown at crates/astrid-cli/src/commands/distro/validate.rs:122

        && semver::VersionReq::parse(av).is_err()
    {
        anyhow::bail!("distro.astrid-version '{av}' is not a valid semver requirement");
    }

    // Requires version strings are valid semver requirements.
    for (ns, ifaces) in &manifest.distro.requires {
        for (name, req) in ifaces {
            if semver::VersionReq::parse(req).is_err() {
                anyhow::bail!(
                    "distro.requires.{ns}.{name} '{req}' is not a valid semver requirement",
                );
            }
        }
    }

    // At least one capsule.
    if manifest.capsules.is_empty() {
        anyhow::bail!("distro must contain at least one capsule");
    }

    // No duplicate capsule names, and each name must be a valid
    // identifier. Names become path components in the `.shuttle` layout
    // (`capsules/<name>.capsule`) and on disk under the capsule store;
    // constraining them to `^[a-z][a-z0-9-]*$` keeps a manifest from
    // introducing `/`, `..`, or other path-hostile characters there.
    let mut seen_names = HashSet::new();
    for cap in &manifest.capsules {
        if !is_valid_id(&cap.name) {
            anyhow::bail!(
                "capsule name '{}' is invalid (must match ^[a-z][a-z0-9-]*$)",
                cap.name,
            );
        }
        if !seen_names.insert(&cap.name) {
            anyhow::bail!("duplicate capsule name '{}'", cap.name);
        }

View on GitHub (pinned to affd8760f4)

Solutions

  1. Add at least one `[[capsules]]` entry pinning a released `version` or `tag`.
  2. Fix the script/template that generated the manifest so it emits capsule entries.
  3. If you truly want no capsules, don't build a distro — use `astrid capsule install` directly.

Example fix

# before
[distro]
name = "my-distro"
# no capsules

# after
[distro]
name = "my-distro"

[[capsules]]
name = "http"
version = "1.2.0"
Defensive patterns

Strategy: validation

Validate before calling

if manifest.capsules.is_empty() {
    panic!("distro manifest has no capsules; add at least one [[capsules]] entry");
}

Try / catch

match validate_manifest(&manifest) {
    Err(e) if e.to_string().contains("at least one capsule") => {
        eprintln!("manifest is empty of capsules — add [[capsules]] entries");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running the distro build/validation with a manifest that declares `distro` metadata but has an empty `capsules` list — e.g. no `[[capsules]]` sections, or all capsule sections removed/commented out.

Common situations: New distro manifests scaffolded empty; bulk-deleting capsule entries while debugging; a generation script that produced no capsules due to an upstream filter.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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