{"record":{"id":"9ea440244d95b579","repo":"astrid-runtime/astrid","slug":"distro-source-source-must-use-owner-repo-a-u","errorCode":null,"errorMessage":"distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive","messagePattern":"distro source '(.+?)' must use @owner/repo, a URL, a local Distro\\.toml path, or a \\.shuttle archive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/init.rs","lineNumber":322,"sourceCode":"\n/// Resolve an explicit remote distro source string to a URL.\n///\n/// - `@org/repo` → `https://raw.githubusercontent.com/org/repo/main/Distro.toml`\n/// - `https://...` → as-is\n///\n/// A bare name has no provenance and is rejected rather than being silently\n/// assigned to an organization by the neutral runtime.\npub(super) fn resolve_distro_url(source: &str) -> anyhow::Result<String> {\n    if source.starts_with(\"http://\") || source.starts_with(\"https://\") {\n        Ok(source.to_string())\n    } else if let Some(repo_path) = source.strip_prefix('@') {\n        let mut segments = repo_path.split('/');\n        let valid = matches!(\n            (segments.next(), segments.next(), segments.next()),\n            (Some(owner), Some(repo), None) if !owner.is_empty() && !repo.is_empty()\n        );\n        if !valid {\n            bail!(\n                \"distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive\"\n            );\n        }\n        Ok(format!(\n            \"https://raw.githubusercontent.com/{repo_path}/main/Distro.toml\"\n        ))\n    } else {\n        bail!(\n            \"distro source '{source}' must use @owner/repo, a URL, a local Distro.toml path, or a .shuttle archive\"\n        )\n    }\n}\n\n/// Parse a comma-separated multi-select entry into a deduped, ordered list\n/// of 1-based indices, dropping anything out of `[1, count]` or unparseable.\n///\n/// Order follows the user's entry; duplicates are collapsed to the first\n/// occurrence so selecting `1,1,2` installs each provider once.","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/init.rs#L304-L340","documentation":"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.","triggerScenarios":"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`.","commonSituations":"Typing just a distro name expecting a registry-style default lookup; extra path segments pasted from a GitHub URL; trailing slash from shell completion.","solutions":["Use the full GitHub shorthand: @owner/repo (exactly two non-empty segments).","Or pass a full URL to the distro's Distro.toml.","Or point at a local Distro.toml file path or a .shuttle archive."],"exampleFix":"// before\nastrid init --source mydistro\n// after\nastrid init --source @acme/mydistro","handlingStrategy":"validation","validationCode":"fn is_owner_repo(s: &str) -> bool {\n    let s = s.strip_prefix('@').unwrap_or(s);\n    let parts: Vec<_> = s.split('/').collect();\n    parts.len() == 2 && parts.iter().all(|p| !p.is_empty())\n}","typeGuard":null,"tryCatchPattern":"match resolve_distro_url(source) {\n    Ok(url) => fetch(url),\n    Err(e) => eprintln!(\"invalid source: {e:#}\"),\n}","preventionTips":["Use @owner/repo exactly (two non-empty segments) for GitHub shorthand.","Strip trailing slashes and extra path segments when building sources in scripts.","Remember bare distro names have no default registry — always qualify."],"tags":["cli","install","validation","distro"],"backgroundTag":"invalid-argument-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}