{"record":{"id":"b5a864672c6cae80","repo":"astrid-runtime/astrid","slug":"seal-can-only-resolve-github-backed-capsule-source","errorCode":null,"errorMessage":"seal can only resolve GitHub-backed capsule sources (@org/repo); got {source:?}","messagePattern":"seal can only resolve GitHub-backed capsule sources \\(@org/repo\\); got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/capsule/install.rs","lineNumber":421,"sourceCode":"/// release assets, so a missing asset is a hard error the maintainer\n/// must resolve.\n/// `name_hint` is the distro capsule `name`, used to pick the right\n/// archive when one source ships several (a monorepo builds/releases one\n/// `.capsule` per capsule crate) — the same `capsule_assets`/`pick_capsule`\n/// selection [`install_from_github`] uses. A single-asset release installs\n/// that one regardless of the hint.\n/// The returned ref is the single source of truth the seal records in\n/// the lock's `resolved_ref`: it is whatever GitHub reported as the\n/// release `tag_name`, never an optimistic guess from the manifest.\npub(crate) async fn resolve_capsule_to_file(\n    source: &str,\n    version: Option<&str>,\n    tag: Option<&str>,\n    name_hint: Option<&str>,\n    dest_path: &Path,\n) -> anyhow::Result<String> {\n    let (org, repo) = parse_github_source(source).ok_or_else(|| {\n        anyhow::anyhow!(\n            \"seal can only resolve GitHub-backed capsule sources (@org/repo); got {source:?}\"\n        )\n    })?;\n\n    // Authenticated when a token is present (see `github_api_client`).\n    let client = github_api_client()?;\n\n    let resolved_ref = resolve_github_ref(&client, &org, &repo, version, tag).await?;\n\n    // Fetch the resolved release's assets and pick the right `<name>.capsule`\n    // (the same selection the install path uses), so a release shipping\n    // several capsules downloads the one the seal asked for rather than the\n    // first. A missing `.capsule` asset is a hard error — seal requires\n    // pre-built release artifacts.\n    let api_url = release_tag_url(&org, &repo, &resolved_ref)?;\n    let response = client\n        .get(&api_url)\n        .send()","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/capsule/install.rs#L403-L439","documentation":"`seal` (capsule installation) resolves capsule sources exclusively from GitHub repositories in `@org/repo` form. `resolve_capsule_to_file` throws this error when `parse_github_source` cannot extract an org/repo pair from the given source string, e.g. when a local path, bare name, URL, or other registry scheme is passed.","triggerScenarios":"Calling `resolve_capsule_to_file` (via `stage_capsule`) with a source that is not `@org/repo`: a local file path, an https:// URL, a bare capsule name without the `@` prefix, or a malformed ref like `@org` or `org/repo` (missing `@`).","commonSituations":"Typing the capsule name without the `@org/` prefix; pointing seal at a locally-built capsule archive instead of a GitHub release; pasting a full GitHub URL instead of the shorthand; typos like `@org/repo/extra`.","solutions":["Use the `@org/repo` form for the source, e.g. `@acme/my-capsule`","If installing from a local file, use the local-path install path instead of `seal`'s GitHub resolver","Check for typos: exactly one `@` at the start and exactly one `/` separating org and repo"],"exampleFix":"// before\nseal --source my-capsule\n// after\nseal --source @acme/my-capsule","handlingStrategy":"validation","validationCode":"fn is_github_source(source: &str) -> bool {\n    let rest = source.strip_prefix('@')?;\n    let (org, repo) = rest.split_once('/')?;\n    Some(()).filter(|_| !org.is_empty() && !repo.is_empty() && !repo.contains('/')).is_some()\n}\nif !is_github_source(source) { /* fix or route to local installer */ }","typeGuard":"fn as_github_source(source: &str) -> Option<(&str, &str)> {\n    let rest = source.strip_prefix('@')?;\n    let (org, repo) = rest.split_once('/')?;\n    (!org.is_empty() && !repo.is_empty() && !repo.contains('/')).then(|| (org, repo))\n}","tryCatchPattern":null,"preventionTips":["Always write sources in @org/repo shorthand","Use local-path install commands for locally built capsules","Validate the source string in wrapper scripts before invoking seal"],"tags":["cli","capsule","github","invalid-source"],"backgroundTag":"incompatible-source-type","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"}