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
- Paste a full URL on a supported host: https://github.com/<owner>/<repo>/... or https://skills.sh/...
- Re-copy the link from the browser address bar of the GitHub repo/skill folder so the scheme is included.
- If the skill lives on GitLab/Bitbucket, it must be mirrored to GitHub before import — those hosts are unsupported by design.
- 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
- Copy URLs from the browser address bar (includes scheme, avoids the schemeless path entirely).
- Client-side hostname validation before submission catches forge links early.
- Mirror non-GitHub skills to GitHub instead of trying to make the importer accept other hosts.
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
- unsupported URL scheme: {scheme}
- Invalid GitHub URL
- {context} must stay on GitHub (got {host or 'unknown host'})
- URL is required
- skills.sh did not redirect to GitHub — open the skill's repo
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/bdf05fd6b6cdc59e.
Report an issue: GitHub.