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 that

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Complete the spec: 'github:owner/repo'.
  2. When generating specs in code, format with 'github:{owner}/{repo}' and reject empty components first.
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/960485760c17f445. Report an issue: GitHub.