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
- Paste a repository URL at minimum: https://github.com/<owner>/<repo>; ideally the exact skill folder: .../tree/<branch>/skills/<name>.
- Verify the URL in a browser — it should open the repo page, not a dashboard/search page.
- Check the config/env feeding the URL for a missing repo component.
- 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
- Paste URLs only from an open repository page's address bar.
- Client-side check: github.com URLs must contain at least two non-empty path segments.
- Trim whitespace/newlines from clipboard URLs before use.
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
- {context} must stay on GitHub (got {host or 'unknown host'})
- unsupported URL scheme: {scheme}
- Only GitHub or skills.sh URLs are supported
- skills.sh did not redirect to GitHub — open the skill's repo
- Invalid raw GitHub URL
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/557c2f976825d4ae.
Report an issue: GitHub.