astrid-runtime/astrid · error

distro source '{source}' must use @owner/repo, a URL, a loca

Error message

distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive

What it means

resolve_distro_url only accepts a repo path in @owner/repo form; a source that looks like a bare repo path but doesn't split into exactly two non-empty '/'-separated segments is rejected. The source must instead be a URL, a local Distro.toml path, or a .shuttle archive.

Source

Thrown at crates/astrid-cli/src/commands/init.rs:322

/// Resolve an explicit remote distro source string to a URL.
///
/// - `@org/repo` → `https://raw.githubusercontent.com/org/repo/main/Distro.toml`
/// - `https://...` → as-is
///
/// A bare name has no provenance and is rejected rather than being silently
/// assigned to an organization by the neutral runtime.
pub(super) fn resolve_distro_url(source: &str) -> anyhow::Result<String> {
    if source.starts_with("http://") || source.starts_with("https://") {
        Ok(source.to_string())
    } else if let Some(repo_path) = source.strip_prefix('@') {
        let mut segments = repo_path.split('/');
        let valid = matches!(
            (segments.next(), segments.next(), segments.next()),
            (Some(owner), Some(repo), None) if !owner.is_empty() && !repo.is_empty()
        );
        if !valid {
            bail!(
                "distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive"
            );
        }
        Ok(format!(
            "https://raw.githubusercontent.com/{repo_path}/main/Distro.toml"
        ))
    } else {
        bail!(
            "distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive"
        )
    }
}

/// Parse a comma-separated multi-select entry into a deduped, ordered list
/// of 1-based indices, dropping anything out of `[1, count]` or unparseable.
///
/// Order follows the user's entry; duplicates are collapsed to the first
/// occurrence so selecting `1,1,2` installs each provider once.

View on GitHub (pinned to affd8760f4)

Solutions

  1. Use the full GitHub shorthand: @owner/repo (exactly two non-empty segments).
  2. Or pass a full URL to the distro's Distro.toml.
  3. Or point at a local Distro.toml file path or a .shuttle archive.

Example fix

// before
astrid init --source mydistro
// after
astrid init --source @acme/mydistro
Defensive patterns

Strategy: validation

Validate before calling

fn is_owner_repo(s: &str) -> bool {
    let s = s.strip_prefix('@').unwrap_or(s);
    let parts: Vec<_> = s.split('/').collect();
    parts.len() == 2 && parts.iter().all(|p| !p.is_empty())
}

Try / catch

match resolve_distro_url(source) {
    Ok(url) => fetch(url),
    Err(e) => eprintln!("invalid source: {e:#}"),
}

Prevention

When it happens

Trigger: Passing a distro source that is neither a URL, a local Distro.toml path, a .shuttle archive, nor a two-segment non-empty owner/repo path — e.g. `--source mydistro` (bare name), `--source owner/` (empty repo), or `--source a/b/c`.

Common situations: Typing just a distro name expecting a registry-style default lookup; extra path segments pasted from a GitHub URL; trailing slash from shell completion.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/9ea440244d95b579. Report an issue: GitHub.