{"record":{"id":"557c2f976825d4ae","repo":"odysseus-dev/odysseus","slug":"invalid-github-url","errorCode":null,"errorMessage":"Invalid GitHub URL","messagePattern":"Invalid GitHub URL","errorType":"validation","errorClass":"SkillImportError","httpStatus":null,"severity":"error","filePath":"services/memory/skill_importer.py","lineNumber":326,"sourceCode":"\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\")\n        owner, repo, ref = bits[0], bits[1], bits[2]\n        path = \"/\".join(bits[3:])\n        return ResolvedSource(owner=owner, repo=repo, ref=ref, path=path)\n\n    bits = [p for p in parsed.path.split(\"/\") if p]\n    if len(bits) < 2:\n        raise SkillImportError(\"Invalid GitHub URL\")\n    owner, repo = bits[0], bits[1]\n    ref = \"main\"\n    path = \"\"\n\n    if len(bits) >= 4 and bits[2] in (\"tree\", \"blob\"):\n        ref = bits[3]\n        path = \"/\".join(bits[4:])\n    elif len(bits) == 2:\n        path = \"\"\n    else:\n        raise SkillImportError(\"GitHub URL must include /tree/<branch>/... or /blob/<branch>/...\")\n\n    return ResolvedSource(owner=owner, repo=repo, ref=ref, path=path)\n\n\ndef _raw_url(src: ResolvedSource, rel_path: str) -> str:\n    rel = _safe_relpath(rel_path)\n    return f\"https://raw.githubusercontent.com/{src.owner}/{src.repo}/{quote(src.ref, safe='')}/{quote(rel, safe='/')}\"","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/services/memory/skill_importer.py#L308-L344","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nimport_skill(\"https://github.com/owner\")\nSkillImportError: Invalid GitHub URL\n\n# after\nimport_skill(\"https://github.com/owner/repo/tree/main/skills/foo\")","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef has_owner_repo(url: str) -> bool:\n    bits = [p for p in urlparse(url).path.split('/') if p]\n    return len(bits) >= 2","typeGuard":"def is_repo_scoped_github_url(url: str) -> bool:\n    u = urlparse(url.strip())\n    return (u.hostname or '').lower() in _GITHUB_HOSTS and has_owner_repo(u.geturl())","tryCatchPattern":"try:\n    src = parse_skill_source(url)\nexcept SkillImportError as e:\n    if str(e) == 'Invalid GitHub URL':\n        return bad_request('URL must include owner/repo, e.g. https://github.com/owner/repo/tree/main/skills/name')\n    raise","preventionTips":["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."],"tags":["url-validation","github","user-input","skill-import"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}