{"record":{"id":"dc2c1461036e009a","repo":"langchain-ai/deepagents","slug":"github-marketplace-urls-must-contain-exactly-owner","errorCode":null,"errorMessage":"GitHub marketplace URLs must contain exactly owner/repo","messagePattern":"GitHub marketplace URLs must contain exactly owner/repo","errorType":"exception","errorClass":"MarketplaceError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/plugins/marketplace.py","lineNumber":190,"sourceCode":"        except ValueError as exc:\n            msg = \"Invalid marketplace URL\"\n            raise MarketplaceError(msg) from exc\n        path = parsed.path\n        if path.endswith(\".git\") or \"/_git/\" in path:\n            return RepositoryMarketplaceSource(\n                source_type=\"git\", value=url, ref=ref or None\n            )\n        if parsed.hostname in {\"github.com\", \"www.github.com\"}:\n            parts = [part for part in path.split(\"/\") if part]\n            if len(parts) == _GITHUB_REPO_PART_COUNT:\n                repo_path = \"/\".join(parts)\n                git_url = urlunparse(parsed._replace(path=f\"/{repo_path}.git\"))\n                return RepositoryMarketplaceSource(\n                    source_type=\"git\", value=git_url, ref=ref or None\n                )\n            if len(parts) > _GITHUB_REPO_PART_COUNT:\n                msg = \"GitHub marketplace URLs must contain exactly owner/repo\"\n                raise MarketplaceError(msg)\n        return UrlMarketplaceSource(source_type=\"url\", value=url)\n\n    if value.startswith((\"./\", \"../\", \"/\", \"~\")):\n        return _marketplace_source_from_path(value)\n\n    # Bare relative paths such as `marketplace` (no ./ prefix) are accepted when\n    # they exist on disk, before GitHub-shorthand parsing.\n    candidate = Path(value).expanduser()\n    if candidate.exists():\n        return _marketplace_source_from_path(value)\n\n    repo, sep, ref = value.replace(\"#\", \"@\", 1).partition(\"@\")\n    if (\n        \"/\" in value\n        and \":\" not in value\n        and not value.startswith(\"@\")\n        and _GITHUB_REPO_RE.match(repo)\n    ):","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/plugins/marketplace.py#L172-L208","documentation":"For `https://github.com/...` URLs that are not `.git`-suffixed, the parser splits the path and expects exactly two segments (`owner/repo`) to convert the URL into a GitHub repository source (marketplace.py:180-190). If the path has more than two segments — e.g. a deep link into a subdirectory, a blob/tree URL, or an issues page — the parser refuses rather than guessing, since a marketplace must map to a whole repository.","triggerScenarios":"`parse_marketplace_source('https://github.com/owner/repo/tree/main/marketplace.json')`, `'https://github.com/owner/repo/blob/main/.claude-plugin/marketplace.json'`, or any GitHub URL with 3+ path segments, via `add_marketplace_source` or `_plugin_repository_source`.","commonSituations":"Pasting a GitHub web UI URL (tree/blob view) instead of the repo root; linking to a marketplace.json file inside a repo rather than the repo itself; trailing deep paths from GitHub search results.","solutions":["Use the repository root URL `https://github.com/owner/repo` (exactly two path segments).","Use the GitHub shorthand `owner/repo` instead of a full URL.","If you truly want a file served over HTTPS, host the raw marketplace.json at a non-GitHub URL or use `https://raw.githubusercontent.com/...` only if it ends with `.json` outside the github.com host check (raw.githubusercontent.com is treated as a plain URL source)."],"exampleFix":"// before\nadd_marketplace_source(\"https://github.com/owner/repo/tree/main/.claude-plugin\")\n// after\nadd_marketplace_source(\"https://github.com/owner/repo\")","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef validate_github_repo_url(raw: str) -> str:\n    url = raw.partition(\"#\")[0]\n    parsed = urlparse(url)\n    if parsed.hostname in {\"github.com\", \"www.github.com\"}:\n        parts = [p for p in parsed.path.split(\"/\") if p]\n        if len(parts) != 2:\n            raise ValueError(f\"Use https://github.com/owner/repo, got: {url}\")\n    return raw","typeGuard":"from urllib.parse import urlparse\n\ndef is_github_repo_root(value: str) -> bool:\n    parsed = urlparse(value.partition(\"#\")[0])\n    if parsed.hostname not in {\"github.com\", \"www.github.com\"}:\n        return False\n    return len([p for p in parsed.path.split(\"/\") if p]) == 2","tryCatchPattern":"try:\n    source = parse_marketplace_source(raw)\nexcept MarketplaceError as exc:\n    if \"exactly owner/repo\" in str(exc):\n        # Reduce a deep GitHub web URL to the repo root\n        parts = [p for p in urlparse(raw).path.split(\"/\") if p]\n        raw = f\"https://github.com/{parts[0]}/{parts[1]}\"\n        source = parse_marketplace_source(raw)\n    else:\n        raise","preventionTips":["Use the `owner/repo` shorthand for GitHub marketplaces instead of full web URLs.","Never paste GitHub tree/blob/issue URLs as marketplace sources.","Document that a GitHub marketplace source must map to a whole repository."],"tags":["validation","github","url"],"backgroundTag":"invalid-marketplace-source","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}