{"record":{"id":"f32a3e5813c539d6","repo":"astrid-runtime/astrid","slug":"capsule-tag-must-not-be-empty","errorCode":null,"errorMessage":"capsule '{}': tag must not be empty","messagePattern":"capsule '(.+?)': tag must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":179,"sourceCode":"        }\n        if !version.is_empty() && Version::parse(version).is_err() {\n            anyhow::bail!(\n                \"capsule '{}': version '{}' is not valid semver\",\n                cap.name,\n                cap.version\n            );\n        }\n        let tag = cap.tag.as_deref().map(str::trim);\n        if let Some(raw_tag) = cap.tag.as_deref()\n            && raw_tag != raw_tag.trim()\n        {\n            anyhow::bail!(\n                \"capsule '{}': tag must not contain surrounding whitespace\",\n                cap.name\n            );\n        }\n        if matches!(tag, Some(\"\")) {\n            anyhow::bail!(\"capsule '{}': tag must not be empty\", cap.name);\n        }\n        if version.is_empty() && tag.is_none() {\n            anyhow::bail!(\n                \"capsule '{}': distro capsules require a concrete released version or tag\",\n                cap.name\n            );\n        }\n    }\n\n    // At least one uplink.\n    let has_uplink = manifest\n        .capsules\n        .iter()\n        .any(|c| c.role.as_deref() == Some(\"uplink\"));\n    if !has_uplink {\n        anyhow::bail!(\"distro must have at least one capsule with role = \\\"uplink\\\" (a frontend)\");\n    }\n","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L161-L197","documentation":"A capsule may specify a tag, but a tag that is (or trims to) the empty string is meaningless for release resolution, so validate_manifest bails with this error when `tag` is `Some(\"\")` after trimming. This keeps tag-based capsule pins unambiguous.","triggerScenarios":"A `[[capsules]]` entry with `tag = \"\"` (or `tag = \"   \"`, which trims to empty and passes the whitespace check only if raw==trim... — practically, an explicitly empty/blank tag value).","commonSituations":"A templating/generation script that emits `tag = \"${TAG}\"` with an unset variable; manually clearing a tag value instead of deleting the field; YAML/TOML quoting an empty string by mistake.","solutions":["Delete the `tag` field entirely if you don't intend to pin a tag (then supply a `version`).","Set the tag to the actual release tag, e.g. `tag = \"v1.2.0\"`.","Fix the generating script to omit the field when the tag variable is empty."],"exampleFix":"# before\n[[capsules]]\nname = \"http\"\ntag = \"\"\n\n# after\n[[capsules]]\nname = \"http\"\ntag = \"v1.2.0\"","handlingStrategy":"validation","validationCode":"for cap in &manifest.capsules {\n    if cap.tag.as_deref().map(str::trim) == Some(\"\") {\n        panic!(\"capsule '{}': tag is empty; remove it or set a real tag\", cap.name);\n    }\n}","typeGuard":"fn is_meaningful_tag(t: &Option<String>) -> bool {\n    !matches!(t.as_deref().map(str::trim), Some(\"\"))\n}","tryCatchPattern":"if let Err(e) = validate_manifest(&manifest) {\n    if e.to_string().contains(\"tag must not be empty\") {\n        eprintln!(\"remove the empty tag or set a real one: {e}\");\n    }\n}","preventionTips":["Omit the tag field instead of writing an empty string","Default empty template variables to omitting the field, not quoting them","Verify generated manifests have no blank tag values before building"],"tags":["cli","manifest","empty-value","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"}