{"record":{"id":"62b8c26f78d97576","repo":"astrid-runtime/astrid","slug":"capsule-env-key-references-undefined-variab","errorCode":null,"errorMessage":"capsule '{}' env.{key} references undefined variable '{{{{ {var_ref} }}}}'","messagePattern":"capsule '(.+?)' env\\.(.+?) references undefined variable '(.+?) \\}\\}\\}\\}'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":204,"sourceCode":"        }\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\n    // Variable references in capsule env.\n    let defined_vars: HashSet<&str> = manifest.variables.keys().map(String::as_str).collect();\n    for cap in &manifest.capsules {\n        for (key, value) in &cap.env {\n            for var_ref in extract_variable_refs(value) {\n                if !defined_vars.contains(var_ref) {\n                    anyhow::bail!(\n                        \"capsule '{}' env.{key} references undefined variable '{{{{ {var_ref} }}}}'\",\n                        cap.name,\n                    );\n                }\n            }\n        }\n    }\n\n    // Invite policy — additive, so the rule is \"if any field is set,\n    // the shape must be coherent\". The kernel still cap-gates issuance\n    // at runtime; this is fail-fast for typos.\n    if let Some(invites) = &manifest.invites {\n        if !invites.issuers.is_empty() && invites.default_group.is_none() {\n            anyhow::bail!(\n                \"invites.issuers is non-empty but invites.default-group is unset — \\\n                 either configure both or remove the [invites] section\"\n            );\n        }","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L186-L222","documentation":"Capsule environment values may interpolate variables defined in the manifest's [variables] table using {{{{ name }}}} syntax. validate_manifest extracts every variable reference from each capsule env value and fails if any referenced name is not defined in manifest.variables.","triggerScenarios":"A capsule env value like `TOKEN = \"{{{{ api_token }}}}\"` where `api_token` has no entry in the manifest's variables table. Triggered during any distro manifest validation.","commonSituations":"Typo in a variable reference; a variable was removed/renamed in [variables] but env values still reference the old name; copying env blocks between distros with different variable sets.","solutions":["Add the missing variable to the manifest's [variables] table.","Fix the typo in the env value so it matches an existing variable name.","Remove the reference if the variable is no longer needed."],"exampleFix":"// before\n[variables]\nregion = \"eu-west\"\nenv: API_URL = \"{{{{ api_endpoint }}}}\"  # undefined\n\n// after\n[variables]\nregion = \"eu-west\"\napi_endpoint = \"https://api.example.com\"\nenv: API_URL = \"{{{{ api_endpoint }}}}\"","handlingStrategy":"validation","validationCode":"fn check_env_vars(capsules: &[Capsule], defined: &HashSet<String>) -> Vec<String> {\n    capsules.iter()\n        .flat_map(|c| c.env.iter().map(move |(k, v)| (c.name.as_str(), k, v)))\n        .flat_map(|(name, k, v)| extract_variable_refs(v)\n            .filter(|r| !defined.contains(*r)).map(move |r| format!(\"{name}: {k} -> {r}\")))\n        .collect()\n}","typeGuard":"fn var_is_defined(r: &str, defined: &HashSet<&str>) -> bool {\n    defined.contains(r)\n}","tryCatchPattern":"let problems = check_env_vars(&manifest.capsules, &defined_vars);\nif !problems.is_empty() {\n    eprintln!(\"undefined variable refs: {problems:?}\");\n    std::process::exit(1);\n}","preventionTips":["Keep [variables] adjacent to env usage and review both together in PRs.","Rename variables with project-wide search/replace, never by hand.","Add a pre-commit lint that runs manifest validation."],"tags":["manifest","validation","variables","template"],"backgroundTag":"missing-config-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}