astrid-runtime/astrid · error
Invalid GitHub URL format. Expected github.com/org/repo or…
Error message
Invalid GitHub URL format. Expected github.com/org/repo or @org/repo
What it means
Raised in install_from_github when extract_github_org_repo cannot derive an (org, repo) pair from the provided URL. The installer accepts github.com/org/repo style URLs or @org/repo shorthand; anything else fails before any network request.
Solutions
- Use the @org/repo shorthand form
- Or use github.com/org/repo without scheme or .git suffix
- Strip https://, git@, trailing .git, and extra path segments from the URL
Example fix
// before --from git@github.com:org/repo.git // after --from @org/repo
Defensive patterns
Strategy: validation
Validate before calling
fn is_installable_github_ref(s: &str) -> bool {
s.starts_with("@") && s[1..].split('/').count() == 2
|| s.starts_with("github.com/") && s["github.com/".len()..].split('/').count() == 2
} Type guard
fn parse_org_repo(s: &str) -> Option<(String, String)> {
let rest = s.strip_prefix("@").or_else(|| s.strip_prefix("github.com/"))?;
let mut it = rest.split('/');
Some((it.next()?.to_string(), it.next()?.to_string()))
} Try / catch
match install_from_github(url).await { Err(e) if e.to_string().contains("Invalid GitHub URL format") => { eprintln!("use @org/repo or github.com/org/repo"); std::process::exit(2) }, other => other } Prevention
- Normalize inputs to @org/repo before invoking install
- Strip schemes, git@ prefixes, .git suffixes, and extra path segments
- Validate the org/repo shape in shell wrappers that build the CLI invocation
When it happens
Trigger: Passing a bare repo name ('myrepo'), a full https:// prefix the extractor doesn't handle, a git SSH URL (git@github.com:org/repo), or a URL with extra path segments the parser rejects.
Common situations: Copy-pasting the clone URL (https://github.com/org/repo.git or SSH form) instead of the supported formats, or passing just the repo slug from memory.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- GitHub releases URL cannot be a base
- seal can only resolve GitHub-backed capsule sources…
- all capsule install(s) failed — not writing Distro.lock…
- an Astrid daemon appears to be running but its uplink is…
- astrid distro apply requires a signed Distro…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/5891f97714666cda.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/capsule/install.rs:318
/// Install from a GitHub source, returning the concrete ref that was
/// actually resolved and fetched (`Some` on the release-asset path). The
/// clone-and-build fallback returns `None` — there is no single release
/// tag it resolved (it builds from whatever `--depth 1` HEAD it cloned).
async fn install_from_github(
url: &str,
name_hint: Option<&str>,
version: Option<&str>,
tag: Option<&str>,
context: InstallContext<'_>,
) -> anyhow::Result<(Vec<InstalledCapsuleOutcome>, Option<String>)> {
// Authenticated when a token is present so release resolution isn't
// throttled at the anonymous 60/hr limit mid-distro (see
// `github_api_client`).
let client = github_api_client()?;
let (org, repo) = extract_github_org_repo(url).ok_or_else(|| {
anyhow::anyhow!("Invalid GitHub URL format. Expected github.com/org/repo or @org/repo")
})?;
// Whether the caller pinned a concrete release. A pin is a hard
// contract: if it cannot be honored we fail loudly rather than build
// HEAD, which would install something other than what was pinned and
// break the reproducibility the pin exists to guarantee.
let pinned = version.is_some() || tag.is_some();
// Priority 1: download packed `.capsule` archive(s) from the release
// resolved by version/tag (or latest when unpinned). Each archive
// contains everything an install needs (WASM, manifest, bundled WIT
// definitions). The ref resolved here is the *actually resolved* tag —
// the single source of truth threaded into the lock; we never silently
// fall back to `releases/latest` when a version/tag is pinned.
match resolve_github_ref(&client, org, repo, version, tag).await {
Ok(resolved_ref) => {
// Fetch the resolved release's assets. Build the URL via
// `release_tag_url` so a tag containing `/` is percent-encoded asView on GitHub (pinned to affd8760f4)