odysseus-dev/odysseus · error · SkillImportError

Invalid raw GitHub URL

Error message

Invalid raw GitHub URL

What it means

Raised by parse_skill_source (services/memory/skill_importer.py) when a raw.githubusercontent.com URL has fewer than 4 non-empty path segments. Raw URLs must follow /owner/repo/ref/path/to/file — the importer maps segments 1-3 to owner/repo/ref and the rest to the in-repo path, so a URL like https://raw.githubusercontent.com/owner/repo or .../owner/repo/main cannot identify a file and is rejected as malformed.

Source

Thrown at services/memory/skill_importer.py:319

            raise SkillImportError(
                "skills.sh did not redirect to GitHub — open the skill's "
                "repository on GitHub, navigate to the exact skill folder or "
                "SKILL.md file, and paste that URL; the repository-root link "
                "alone is not sufficient"
            )
        url = final

    # 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>/...")

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Use a complete raw URL including ref and file path: https://raw.githubusercontent.com/<owner>/<repo>/<ref>/skills/<name>/SKILL.md.
  2. Get the URL the reliable way: open the file on github.com, click the Raw button, copy the address bar.
  3. If the branch contains slashes, that is fine — segment 3 onward is treated as ref+path only when counts line up; prefer the tree/blob web URL form to avoid ambiguity.
  4. Verify the URL resolves: curl -I <raw url> should return 200.

Example fix

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

# after
import_skill("https://raw.githubusercontent.com/owner/repo/main/skills/foo/SKILL.md")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def parse_raw_url(url: str) -> tuple[str, str, str, str]:
    bits = [p for p in urlparse(url).path.split('/') if p]
    if urlparse(url).hostname != 'raw.githubusercontent.com' or len(bits) < 4:
        raise ValueError('raw URL must be https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path>')
    return bits[0], bits[1], bits[2], '/'.join(bits[3:])

Type guard

def is_valid_raw_github_url(url: str) -> bool:
    try:
        parse_raw_url(url)
        return True
    except ValueError:
        return False

Try / catch

try:
    src = parse_skill_source(raw_url)
except SkillImportError as e:
    if str(e) == 'Invalid raw GitHub URL':
        return bad_request('Raw URLs need owner/repo/branch/file, e.g. .../raw.githubusercontent.com/o/r/main/SKILL.md')
    raise

Prevention

When it happens

Trigger: Pasting https://raw.githubusercontent.com/<owner>/<repo> (repo root), .../<owner>/<repo>/<branch> (branch with no file), or a URL whose path collapsed to empty (trailing garbage after the host); hand-constructing a raw URL and forgetting the file component.

Common situations: Users trimming the URL while sharing; copying the raw base from docs examples and appending only owner/repo; branch names with slashes making the segment count look right but the intended file missing.

Related errors


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