{"record":{"id":"37bb9ee2d3d74a54","repo":"odysseus-dev/odysseus","slug":"skills-sh-did-not-redirect-to-github-open-the-sk","errorCode":null,"errorMessage":"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","messagePattern":"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","errorType":"validation","errorClass":"SkillImportError","httpStatus":null,"severity":"error","filePath":"services/memory/skill_importer.py","lineNumber":301,"sourceCode":"        url = \"https://\" + url\n\n    parsed = urlparse(url)\n    hostname = (parsed.hostname or \"\").lower()\n    if hostname not in _GITHUB_HOSTS and hostname not in _SKILLS_SH_HOSTS:\n        raise SkillImportError(\"Only GitHub or skills.sh URLs are supported\")\n\n    # A skills.sh link is only usable if it redirects to an exact supported\n    # GitHub host. Scraping the page body for a github.com link cannot work:\n    # skill pages only ever link the repository root, never the skill's\n    # subdirectory, so the scrape resolves every skill in a repo to the same\n    # (wrong) bundle. Fail with an actionable message instead.\n    if hostname in _SKILLS_SH_HOSTS:\n        r = _get_checked(url, timeout=20.0)\n        if r.status_code >= 400:\n            raise _github_response_error(r)\n        final = str(r.url)\n        if _github_host(final) not in _GITHUB_HOSTS:\n            raise SkillImportError(\n                \"skills.sh did not redirect to GitHub — open the skill's \"\n                \"repository on GitHub, navigate to the exact skill folder or \"\n                \"SKILL.md file, and paste that URL; the repository-root link \"\n                \"alone is not sufficient\"\n            )\n        url = final\n\n    # Update parsed and hostname to reflect the new GitHub URL\n    parsed = urlparse(url)\n    hostname = (parsed.hostname or \"\").lower()\n\n    _assert_github_url(url)\n\n    if hostname == \"raw.githubusercontent.com\":\n        # /owner/repo/ref/path/to/file\n        bits = [p for p in parsed.path.split(\"/\") if p]\n        if len(bits) < 4:\n            raise SkillImportError(\"Invalid raw GitHub URL\")","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/services/memory/skill_importer.py#L283-L319","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Verify what the link redirects to: curl -sIL <skills.sh url> | grep -i '^location' — confirm the final hop is github.com.","If the repo moved, find its new GitHub home and import from there.","If you rely on a mirror domain, note it is unsupported by design; import the repo's canonical GitHub URL."],"exampleFix":"# before\nimport_skill(\"https://skills.sh/owner/repo/skill\")   # redirect lands on skills.sh CDN\nSkillImportError: skills.sh did not redirect to GitHub — ...\n\n# after\n# browse to the skill on GitHub and paste the exact folder URL\nimport_skill(\"https://github.com/owner/repo/tree/main/skills/skill\")","handlingStrategy":"fallback","validationCode":"def resolve_skills_sh_url(url: str) -> str | None:\n    with httpx.Client(follow_redirects=False, timeout=20) as c:\n        r = c.get(url)\n    final = str(r.url)\n    return final if (urlparse(final).hostname or '').lower() in _GITHUB_HOSTS and r.status_code < 400 else None","typeGuard":"def skills_sh_redirects_to_github(url: str) -> bool:\n    return resolve_skills_sh_url(url) is not None","tryCatchPattern":"from services.memory.skill_importer import SkillImportError\n\ntry:\n    src = parse_skill_source(skills_sh_url)\nexcept SkillImportError as e:\n    if 'did not redirect to GitHub' in str(e):\n        # fallback: user provides the exact GitHub folder URL manually\n        src = parse_skill_source(prompt_user_for_github_url())\n    else:\n        raise","preventionTips":["Teach users to paste the github.com tree/blob URL of the exact skill folder, not the skills.sh link.","When skills.sh behaves oddly, verify with curl -sIL where its redirects actually land.","Do not scrape page bodies for GitHub links — the source comments explain why that resolves every skill to the wrong bundle."],"tags":["redirects","url-validation","skills-sh","skill-import","github"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}