odysseus-dev/odysseus · error · SkillImportError

skills.sh did not redirect to GitHub — open the skill's…

Error message

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

What it means

Raised by parse_skill_source (services/memory/skill_importer.py) when a skills.sh URL was fetched but its final redirect target (r.url after the validated redirect chain) is not on a GitHub host. skills.sh is only usable as an indirection to GitHub: the importer follows its redirect, and if it does not end on GitHub it refuses — deliberately, because scraping the skills.sh page body cannot resolve the correct skill subdirectory (pages only link the repo root), which would silently import the wrong bundle.

Solutions

  1. Open the skill's page on skills.sh in a browser, click through to GitHub, navigate to the exact skill folder (or SKILL.md), and paste that github.com URL directly.
  2. Verify what the link redirects to: curl -sIL <skills.sh url> | grep -i '^location' — confirm the final hop is github.com.
  3. If the repo moved, find its new GitHub home and import from there.
  4. If you rely on a mirror domain, note it is unsupported by design; import the repo's canonical GitHub URL.

Example fix

# before
import_skill("https://skills.sh/owner/repo/skill")   # redirect lands on skills.sh CDN
SkillImportError: skills.sh did not redirect to GitHub — ...

# after
# browse to the skill on GitHub and paste the exact folder URL
import_skill("https://github.com/owner/repo/tree/main/skills/skill")
Defensive patterns

Strategy: fallback

Validate before calling

def resolve_skills_sh_url(url: str) -> str | None:
    with httpx.Client(follow_redirects=False, timeout=20) as c:
        r = c.get(url)
    final = str(r.url)
    return final if (urlparse(final).hostname or '').lower() in _GITHUB_HOSTS and r.status_code < 400 else None

Type guard

def skills_sh_redirects_to_github(url: str) -> bool:
    return resolve_skills_sh_url(url) is not None

Try / catch

from services.memory.skill_importer import SkillImportError

try:
    src = parse_skill_source(skills_sh_url)
except SkillImportError as e:
    if 'did not redirect to GitHub' in str(e):
        # fallback: user provides the exact GitHub folder URL manually
        src = parse_skill_source(prompt_user_for_github_url())
    else:
        raise

Prevention

When it happens

Trigger: skills.sh changes behavior and serves content directly (or redirects to its own CDN) instead of to github.com; a skills.sh URL for a skill whose repo was deleted or moved off GitHub; network middleware rewriting the redirect target; a skills.sh vanity link that redirects to a non-allowlisted mirror like gitmirror.com.

Common situations: skills.sh outage or redesign days; deleted/moved upstream repositories; mirrors and proxies (ghproxy, gitmirror) used in regions with poor GitHub access; stale skills.sh links shared in chat.

Related errors


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

Appendix: source

Thrown at services/memory/skill_importer.py:301

        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:
            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")

View on GitHub (pinned to f9235ebbf1)