Hmbown/CodeWhale · error
github source must be 'github:owner/repo' (got {spec})
Error message
github source must be 'github:owner/repo' (got {spec}) What it means
InstallSource::parse splits a 'github:'-prefixed spec on its first '/'; when there is no slash at all, split_once returns None and this context error attaches. 'github:foo' (owner only, no repo) is the canonical trigger.
Source
Thrown at crates/tui/src/skills/install.rs:130
/// [`InstallSource::GitHubRepo`]
/// * any other `http://` or `https://` prefix → [`InstallSource::DirectUrl`]
/// * anything else → [`InstallSource::Registry`]
pub fn parse(spec: &str) -> Result<Self> {
let trimmed = spec.trim();
if trimmed.is_empty() {
bail!("install source must not be empty");
}
if let Some(rest) = trimmed.strip_prefix("github:") {
let rest = rest.trim();
// Reject obviously bogus values up front. We intentionally accept
// case-insensitive owner/repo so `github:Hmbown/Foo` works.
let (owner, repo) = rest.split_once('/').with_context(|| {
format!("github source must be 'github:owner/repo' (got {spec})")
})?;
let owner = owner.trim();
let repo = repo.trim().trim_end_matches('/');
if owner.is_empty() || repo.is_empty() {
bail!("github source must be 'github:owner/repo' (got {spec})");
}
if owner.contains('/') || repo.contains('/') {
bail!("github source must be 'github:owner/repo' (got {spec})");
}
return Ok(Self::GitHubRepo(format!("{owner}/{repo}")));
}
if trimmed.starts_with("https://") || trimmed.starts_with("http://") {
if let Some(repo) = parse_github_browser_url(trimmed) {
return Ok(Self::GitHubRepo(repo));
}
return Ok(Self::DirectUrl(trimmed.to_string()));
}
Ok(Self::Registry(trimmed.to_string()))
}
}
/// Detect bare `https://github.com/<owner>/<repo>` URLs (with or without a
/// trailing `.git`) and return `owner/repo`. Returns `None` for any URL thatView on GitHub (pinned to 0c42157ee5)
Solutions
- Complete the spec: 'github:owner/repo'.
- When generating specs in code, format with 'github:{owner}/{repo}' and reject empty components first.
- For URLs with subpaths (monorepo subdirs), use the full https:// form; the github: shorthand only takes owner/repo.
Example fix
# before /skill install github:zai # after /skill install github:zai/codewhale
Defensive patterns
Strategy: validation
Validate before calling
fn valid_github_spec(spec: &str) -> bool {
let Some(rest) = spec.trim().strip_prefix("github:") else { return true };
matches!(rest.split_once('/'), Some((o, r)) if !o.trim().is_empty() && !r.trim().is_empty())
} Prevention
- Build github: specs with a format! template that rejects empty components.
- Prefer pasting full https://github.com/owner/repo URLs; the parser converts them.
- Remember the shorthand takes exactly owner/repo, no subpaths.
When it happens
Trigger: '/skill install github:zai' where the repo part is missing, or 'github:' followed by a whitespace-only rest after trimming.
Common situations: Truncated copy-paste of a repo slug, specs built by string concatenation where the repo variable was empty, and forgetting the shorthand needs both owner and repo even when they look identical.
Related errors
- install source must not be empty
- failed to download skill (last status: {})
- DSH plugin path not available: {reason}
- skill '{name}' was imported locally (spec '{}') and cannot b
- skill '{name}' is not installed at {}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/960485760c17f445.
Report an issue: GitHub.