odysseus-dev/odysseus · error · SkillImportError

Only GitHub or skills.sh URLs are supported

Error message

Only GitHub or skills.sh URLs are supported

What it means

First host check in parse_skill_source (services/memory/skill_importer.py): for a schemeless URL like 'github.com/owner/repo', the code prepends '//' to extract a rough hostname, and if that host is neither a GitHub host nor a skills.sh host it rejects before upgrading the URL to https. This catches pasted bare domains that are not supported sources, at a point where no scheme context exists yet.

Source

Thrown at services/memory/skill_importer.py:282

def parse_skill_source(url: str) -> ResolvedSource:
    """Normalize skills.sh / GitHub web URLs into owner/repo/ref/path."""
    url = (url or "").strip()
    if not url:
        raise SkillImportError("URL is required")

    # ``urlparse`` only reports an unambiguous scheme when the URL carries the
    # ``scheme://`` form. Opaque schemes (``mailto:``, ``javascript:``) and a
    # schemeless ``host:port`` both parse a "scheme" that is not one, so they
    # fall through to the host check below and are rejected on the host instead.
    scheme = urlparse(url).scheme.lower()
    if scheme not in ("http", "https"):
        if scheme and url.lower().startswith(f"{scheme}://"):
            raise SkillImportError(f"unsupported URL scheme: {scheme}")
        # Schemeless "github.com/owner/repo" — accept only a supported host.
        rough_host = (urlparse("//" + url).hostname or "").lower()
        if rough_host not in _GITHUB_HOSTS and rough_host not in _SKILLS_SH_HOSTS:
            raise SkillImportError("Only GitHub or skills.sh URLs are supported")
        url = "https://" + url

    parsed = urlparse(url)
    hostname = (parsed.hostname or "").lower()
    if hostname not in _GITHUB_HOSTS and hostname not in _SKILLS_SH_HOSTS:
        raise SkillImportError("Only GitHub or skills.sh URLs are supported")

    # A skills.sh link is only usable if it redirects to an exact supported
    # GitHub host. Scraping the page body for a github.com link cannot work:
    # skill pages only ever link the repository root, never the skill's
    # subdirectory, so the scrape resolves every skill in a repo to the same
    # (wrong) bundle. Fail with an actionable message instead.
    if hostname in _SKILLS_SH_HOSTS:
        r = _get_checked(url, timeout=20.0)
        if r.status_code >= 400:
            raise _github_response_error(r)
        final = str(r.url)
        if _github_host(final) not in _GITHUB_HOSTS:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Paste a full URL on a supported host: https://github.com/<owner>/<repo>/... or https://skills.sh/...
  2. Re-copy the link from the browser address bar of the GitHub repo/skill folder so the scheme is included.
  3. If the skill lives on GitLab/Bitbucket, it must be mirrored to GitHub before import — those hosts are unsupported by design.
  4. Strip surrounding text/quotes from the pasted value before submitting.

Example fix

# before
import_skill("gitlab.com/user/repo/skills/foo")
SkillImportError: Only GitHub or skills.sh URLs are supported

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

Strategy: validation

Validate before calling

from urllib.parse import urlparse

_GITHUB = {'github.com', 'www.github.com', 'api.github.com', 'raw.githubusercontent.com'}
_SKILLS = {'skills.sh', 'www.skills.sh'}

def schemeless_host_ok(url: str) -> bool:
    host = (urlparse('//' + url.strip()).hostname or '').lower()
    return host in _GITHUB or host in _SKILLS

Type guard

def is_supported_source(url: str) -> bool:
    u = url.strip()
    host = (urlparse('//' + u).hostname or '').lower() if urlparse(u).scheme not in ('http', 'https') else (urlparse(u).hostname or '').lower()
    return host in _GITHUB or host in _SKILLS

Try / catch

try:
    src = parse_skill_source(url)
except SkillImportError as e:
    if 'Only GitHub or skills.sh' in str(e):
        return bad_request('Unsupported source. Paste a github.com or skills.sh skill URL.')
    raise

Prevention

When it happens

Trigger: Pasting 'gitlab.com/user/repo', 'bitbucket.org/team/repo', 'example.com/skill', or bare text that happens to parse with a hostname ('notahost/foo'); also schemeless strings whose first segment looks like host:port and parses differently than intended.

Common situations: Users pasting a GitLab/Bitbucket URL without the scheme; pasting a skill's homepage rather than its repo; drag-pasting text with a leading token that urlparse treats as a host.

Related errors


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