astrid-runtime/astrid · error
invites.max-principals must be "unlimited" or a…
Error message
invites.max-principals must be "unlimited" or a non-negative integer (got {cap:?}) What it means
invites.max-principals, when set, must be either the literal string "unlimited" or a parseable non-negative integer (u32). Any other string — negative numbers, floats, text — is rejected by validate_manifest.
Solutions
- Set max-principals to a non-negative integer string, e.g. "100".
- Or set it to "unlimited" if no cap is desired.
- Remove the key entirely if the cap is managed elsewhere.
Example fix
// before [invites] max-principals = "none" // after [invites] max-principals = "unlimited"
Defensive patterns
Strategy: validation
Validate before calling
fn valid_max_principals(cap: &Option<String>) -> bool {
match cap {
None => true,
Some(c) => c == "unlimited" || c.parse::<u32>().is_ok(),
}
} Type guard
fn parse_max_principals(cap: &str) -> Option<MaxPrincipals> {
if cap == "unlimited" { Some(MaxPrincipals::Unlimited) }
else { cap.parse::<u32>().ok().map(MaxPrincipals::Limited) }
} Try / catch
match parse_max_principals(cap_str) {
Some(_) => apply(),
None => eprintln!("max-principals must be \"unlimited\" or a u32, got {cap_str:?}"),
} Prevention
- Use "unlimited" verbatim — never "none"/"null"/"inf".
- Store the cap as an integer in tooling that generates manifests.
- Validate the whole [invites] block before committing.
When it happens
Trigger: A manifest where invites.max-principals is set to a value like "-1", "10.5", "none", or other non-numeric/non-"unlimited" text. Detected during any distro manifest validation.
Common situations: Typing "none" or "null" expecting unlimited semantics; writing a negative cap; adding a unit suffix like "50 users"; locale-formatted numbers with separators.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- invites.issuers is non-empty but invites.default-group is…
- 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/62bce9a0f78c6059.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/validate.rs:230
// 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 the
// values; the parser just refuses obvious garbage.
if let Some(branding) = &manifest.branding {
if let Some(icon) = &branding.icon
&& icon.len() > 64 * 1024
{
anyhow::bail!(
"branding.icon is {} bytes — distros must not embed assets larger than 64 KiB",
icon.len()
);
}
if let Some(color) = &branding.primary_color {
validate_hex_color(color)View on GitHub (pinned to affd8760f4)