astrid-runtime/astrid · error
invites.issuers is non-empty but invites.default-group is…
Error message
invites.issuers is non-empty but invites.default-group is unset — either configure both or remove the [invites] section
What it means
The [invites] block in a distro manifest must be coherent: if issuers is non-empty, default-group must also be set, because issued invites need a target group. validate_manifest enforces this fail-fast to catch typos before the kernel caps issuance at runtime.
Solutions
- Set `default-group = "<group>"` under [invites].
- Or remove the issuers entries if invites should not be issued.
- Or delete the entire [invites] section if invite support is not wanted.
Example fix
// before [invites] issuers = ["alice"] // after [invites] issuers = ["alice"] default-group = "members"
Defensive patterns
Strategy: validation
Validate before calling
fn invites_coherent(invites: &Invites) -> Result<(), String> {
if !invites.issuers.is_empty() && invites.default_group.is_none() {
return Err("issuers set without default-group".into());
}
Ok(())
} Type guard
fn invites_block_ok(i: &Invites) -> bool {
i.issuers.is_empty() || i.default_group.is_some()
} Try / catch
if let Some(invites) = &manifest.invites {
if let Err(e) = invites_coherent(invites) {
eprintln!("[invites]: {e}");
std::process::exit(1);
}
} Prevention
- Treat [invites] as all-or-nothing: configure issuers and default-group in the same change.
- Copy the full [invites] example from docs, not fragments.
- Run distro validation in CI so partial invite config fails fast.
When it happens
Trigger: A manifest with `[invites]` containing one or more issuers but no `default-group` key. Detected during any distro manifest validation.
Common situations: Partially configured invite support — the author listed issuers then stopped before setting default-group; renamed/removed the default-group key by mistake; copied a partial [invites] snippet from docs.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- invites.max-principals must be "unlimited" or a…
- capsule ' ': distro capsules require a concrete released…
- distro must have at least one capsule with role = "uplink"…
- branding.accent-color
- branding.icon is bytes — distros must not embed assets…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/7bde57c583fa7789.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/validate.rs:218
for cap in &manifest.capsules {
for (key, value) in &cap.env {
for var_ref in extract_variable_refs(value) {
if !defined_vars.contains(var_ref) {
anyhow::bail!(
"capsule '{}' env.{key} references undefined variable '{{{{ {var_ref} }}}}'",
cap.name,
);
}
}
}
}
// Invite policy — additive, so the rule is "if any field is set,
// the shape must be coherent". The kernel still cap-gates issuance
// at runtime; this is fail-fast for typos.
if let Some(invites) = &manifest.invites {
if !invites.issuers.is_empty() && invites.default_group.is_none() {
anyhow::bail!(
"invites.issuers is non-empty but invites.default-group is unset — \
either configure both or remove the [invites] section"
);
}
if let Some(exp) = &invites.default_expires {
parse_invite_duration(exp).map_err(|e| anyhow::anyhow!(e))?;
}
if let Some(cap) = &invites.max_principals
&& cap != "unlimited"
&& cap.parse::<u32>().is_err()
{
anyhow::bail!(
"invites.max-principals must be \"unlimited\" or a non-negative integer (got {cap:?})",
);
}
}
// Branding — only structural rails. The dashboard interprets theView on GitHub (pinned to affd8760f4)