{"record":{"id":"54c1c14d0461efe9","repo":"astrid-runtime/astrid","slug":"distro-must-contain-at-least-one-capsule","errorCode":null,"errorMessage":"distro must contain at least one capsule","messagePattern":"distro must contain at least one capsule","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":122,"sourceCode":"        && semver::VersionReq::parse(av).is_err()\n    {\n        anyhow::bail!(\"distro.astrid-version '{av}' is not a valid semver requirement\");\n    }\n\n    // Requires version strings are valid semver requirements.\n    for (ns, ifaces) in &manifest.distro.requires {\n        for (name, req) in ifaces {\n            if semver::VersionReq::parse(req).is_err() {\n                anyhow::bail!(\n                    \"distro.requires.{ns}.{name} '{req}' is not a valid semver requirement\",\n                );\n            }\n        }\n    }\n\n    // At least one capsule.\n    if manifest.capsules.is_empty() {\n        anyhow::bail!(\"distro must contain at least one capsule\");\n    }\n\n    // No duplicate capsule names, and each name must be a valid\n    // identifier. Names become path components in the `.shuttle` layout\n    // (`capsules/<name>.capsule`) and on disk under the capsule store;\n    // constraining them to `^[a-z][a-z0-9-]*$` keeps a manifest from\n    // introducing `/`, `..`, or other path-hostile characters there.\n    let mut seen_names = HashSet::new();\n    for cap in &manifest.capsules {\n        if !is_valid_id(&cap.name) {\n            anyhow::bail!(\n                \"capsule name '{}' is invalid (must match ^[a-z][a-z0-9-]*$)\",\n                cap.name,\n            );\n        }\n        if !seen_names.insert(&cap.name) {\n            anyhow::bail!(\"duplicate capsule name '{}'\", cap.name);\n        }","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L104-L140","documentation":"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.","triggerScenarios":"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.","commonSituations":"New distro manifests scaffolded empty; bulk-deleting capsule entries while debugging; a generation script that produced no capsules due to an upstream filter.","solutions":["Add at least one `[[capsules]]` entry pinning a released `version` or `tag`.","Fix the script/template that generated the manifest so it emits capsule entries.","If you truly want no capsules, don't build a distro — use `astrid capsule install` directly."],"exampleFix":"# before\n[distro]\nname = \"my-distro\"\n# no capsules\n\n# after\n[distro]\nname = \"my-distro\"\n\n[[capsules]]\nname = \"http\"\nversion = \"1.2.0\"","handlingStrategy":"validation","validationCode":"if manifest.capsules.is_empty() {\n    panic!(\"distro manifest has no capsules; add at least one [[capsules]] entry\");\n}","typeGuard":null,"tryCatchPattern":"match validate_manifest(&manifest) {\n    Err(e) if e.to_string().contains(\"at least one capsule\") => {\n        eprintln!(\"manifest is empty of capsules — add [[capsules]] entries\");\n    }\n    other => other?,\n}","preventionTips":["Never delete all [[capsules]] sections when editing distro manifests","Check generator output for zero capsules before invoking the CLI","Keep a minimal working capsule entry in scaffold templates"],"tags":["cli","manifest","validation"],"backgroundTag":"empty-required-field","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"}