astrid-runtime/astrid · error
{e}
Error message
{e} What it means
During manifest validation, if the [invites] block sets a default-expires value, it is run through parse_invite_duration and any error is re-raised verbatim via anyhow::anyhow!(e). So the surfaced message is whatever the duration parser produced — a malformed human duration string (e.g. '30d', '2w', '72h') in invites.default-expires.
Source
Thrown at crates/astrid-cli/src/commands/distro/validate.rs:224
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 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!(View on GitHub (pinned to affd8760f4)
Solutions
- Correct invites.default-expires to a supported duration string with its unit (e.g. '72h', '30d').
- Read the wrapped parse message to see which part of the string was rejected.
- Remove the default-expires key if no default expiry is needed.
Example fix
# before (Distro.toml) [invites] default-expires = "30 days" # after [invites] default-expires = "30d"
Defensive patterns
Strategy: validation
Validate before calling
// Validate the duration string before writing it into Distro.toml
fn plausible_duration(s: &str) -> bool {
let t = s.trim();
!t.is_empty()
&& t[..t.len()-1].parse::<u64>().is_ok()
&& matches!(t.chars().last(), Some('s') | Some('m') | Some('h') | Some('d') | Some('w'))
} Prevention
- Use compact unit forms (72h, 30d) with no spaces.
- Lint Distro.toml with `astrid distro validate` before committing.
- Document accepted duration units next to the [invites] template.
When it happens
Trigger: validate_manifest on a Distro.toml whose invites.default-expires is set to a string parse_invite_duration cannot parse: missing unit ('30'), unsupported unit, negative duration, empty string, or stray whitespace/characters.
Common situations: Distro authors writing '1 month' or '30 days' instead of the accepted compact unit forms; copy-pasting cron-style values; typos like '30dd'; localized unit names.
Understand the failure class
Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.
Related errors
- gateway.idle_shutdown_secs must be positive and representabl
- branding.primary-color: {e}
- branding.accent-color: {e}
- distro.astrid-version {req:?} is not a valid requirement: {e
- capsule archive entry '{requested}' is not a regular file
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f5fc380863263444.
Report an issue: GitHub.