odysseus-dev/odysseus · error · SkillImportError

Invalid GitHub URL

Error message

Invalid GitHub URL

What it means

Raised by parse_skill_source (services/memory/skill_importer.py) when a github.com URL path has fewer than 2 non-empty segments — i.e. no owner/repo pair. GitHub web URLs must at least contain /<owner>/<repo>; the importer defaults ref to 'main' and path to '' at this stage, so anything shorter (bare https://github.com, /org, /settings/profile) cannot name a repository and is rejected.

Source

Thrown at services/memory/skill_importer.py:326

    # Update parsed and hostname to reflect the new GitHub URL
    parsed = urlparse(url)
    hostname = (parsed.hostname or "").lower()

    _assert_github_url(url)

    if hostname == "raw.githubusercontent.com":
        # /owner/repo/ref/path/to/file
        bits = [p for p in parsed.path.split("/") if p]
        if len(bits) < 4:
            raise SkillImportError("Invalid raw GitHub URL")
        owner, repo, ref = bits[0], bits[1], bits[2]
        path = "/".join(bits[3:])
        return ResolvedSource(owner=owner, repo=repo, ref=ref, path=path)

    bits = [p for p in parsed.path.split("/") if p]
    if len(bits) < 2:
        raise SkillImportError("Invalid GitHub URL")
    owner, repo = bits[0], bits[1]
    ref = "main"
    path = ""

    if len(bits) >= 4 and bits[2] in ("tree", "blob"):
        ref = bits[3]
        path = "/".join(bits[4:])
    elif len(bits) == 2:
        path = ""
    else:
        raise SkillImportError("GitHub URL must include /tree/<branch>/... or /blob/<branch>/...")

    return ResolvedSource(owner=owner, repo=repo, ref=ref, path=path)


def _raw_url(src: ResolvedSource, rel_path: str) -> str:
    rel = _safe_relpath(rel_path)
    return f"https://raw.githubusercontent.com/{src.owner}/{src.repo}/{quote(src.ref, safe='')}/{quote(rel, safe='/')}"

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Paste a repository URL at minimum: https://github.com/<owner>/<repo>; ideally the exact skill folder: .../tree/<branch>/skills/<name>.
  2. Verify the URL in a browser — it should open the repo page, not a dashboard/search page.
  3. Check the config/env feeding the URL for a missing repo component.
  4. Re-copy from the address bar with the repo open to avoid truncation.

Example fix

# before
import_skill("https://github.com/owner")
SkillImportError: Invalid GitHub URL

# after
import_skill("https://github.com/owner/repo/tree/main/skills/foo")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def has_owner_repo(url: str) -> bool:
    bits = [p for p in urlparse(url).path.split('/') if p]
    return len(bits) >= 2

Type guard

def is_repo_scoped_github_url(url: str) -> bool:
    u = urlparse(url.strip())
    return (u.hostname or '').lower() in _GITHUB_HOSTS and has_owner_repo(u.geturl())

Try / catch

try:
    src = parse_skill_source(url)
except SkillImportError as e:
    if str(e) == 'Invalid GitHub URL':
        return bad_request('URL must include owner/repo, e.g. https://github.com/owner/repo/tree/main/skills/name')
    raise

Prevention

When it happens

Trigger: Pasting https://github.com or https://github.com/<org-only>; a URL whose path was lost to a paste error or trailing-fragment mangling; URLs to GitHub non-repo pages (notifications, dashboard) fed to the importer; whitespace/newline truncating the path.

Common situations: Users pasting the GitHub homepage or an organization page instead of a repository; clipboard capture cutting the URL short; automation building URLs from a config that lacked the repo value.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/557c2f976825d4ae. Report an issue: GitHub.