{"record":{"id":"aaf9f5780443149d","repo":"BerriAI/litellm","slug":"github-source-must-include-repo-field-e-g-or","errorCode":null,"errorMessage":"GitHub source must include 'repo' field (e.g., 'org/repo')","messagePattern":"GitHub source must include 'repo' field \\(e\\.g\\., 'org/repo'\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py","lineNumber":171,"sourceCode":"        raise HTTPException(\n            status_code=500,\n            detail={\"error\": f\"Failed to generate marketplace: {e}\"},\n        )\n\n\n# Allowlist for git-subdir paths: one or more segments separated by '/'.\n# Each segment must start with an alphanumeric character and contain only\n# alphanumeric characters, dots, hyphens, and underscores.\n# This implicitly blocks '..', leading '/', backslashes, and percent-encoded sequences.\n_VALID_GIT_SUBDIR_PATH_RE: Final = re.compile(r\"^[a-zA-Z0-9][a-zA-Z0-9._-]*(/[a-zA-Z0-9][a-zA-Z0-9._-]*)*$\")\n\n\ndef _validate_plugin_source(source: Mapping[str, str]) -> None:\n    \"\"\"Validate plugin source format, raising HTTPException on invalid input.\"\"\"\n    source_type: Final = source.get(\"source\")\n    if source_type == \"github\":\n        if \"repo\" not in source:\n            raise HTTPException(\n                status_code=400,\n                detail={\"error\": \"GitHub source must include 'repo' field (e.g., 'org/repo')\"},\n            )\n    elif source_type == \"url\":\n        if \"url\" not in source:\n            raise HTTPException(\n                status_code=400,\n                detail={\"error\": \"URL source must include 'url' field (e.g., 'https://github.com/org/repo.git')\"},\n            )\n    elif source_type == \"git-subdir\":\n        if not source.get(\"url\"):\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": \"git-subdir source must include 'url' field (e.g., 'https://github.com/org/repo.git')\"\n                },\n            )\n        if not source.get(\"path\"):","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py#L153-L189","documentation":"400 thrown by _validate_plugin_source() when registering or updating a plugin whose source object has \"source\": \"github\" but no \"repo\" key. LiteLLM stores plugins as git references, and for the github source type the 'repo' field (in 'org/repo' form) is the only required locator.","triggerScenarios":"POST /claude-code/plugins (or PUT /claude-code/plugins/{name}) with a body like {\"name\": \"my-plugin\", \"source\": {\"source\": \"github\"}} — the github source object present but the repo key omitted or misspelled (e.g. \"repository\", \"git_repo\").","commonSituations":"Hand-writing the registration curl/JSON and forgetting the repo; adapting an example that used a \"url\" source and keeping only the source type key; integration code that builds the source dict conditionally and skips repo when it is empty.","solutions":["Add the repo field in org/repo form to the source object: \"source\": {\"source\": \"github\", \"repo\": \"org/my-plugin\"}.","Check for misspellings of the key — it must be exactly 'repo', not 'repository' or 'git_repo'.","If you meant to point at a full git URL instead, switch the source type to \"url\" with a \"url\" field rather than github without repo."],"exampleFix":"# before\ncurl -X POST http://localhost:4000/claude-code/plugins \\\n  -d '{\"name\": \"my-plugin\", \"source\": {\"source\": \"github\"}}'\n\n# after\ncurl -X POST http://localhost:4000/claude-code/plugins \\\n  -d '{\"name\": \"my-plugin\", \"source\": {\"source\": \"github\", \"repo\": \"org/my-plugin\"}}'","handlingStrategy":"validation","validationCode":"def valid_github_source(source: dict) -> bool:\n    return (\n        source.get(\"source\") == \"github\"\n        and isinstance(source.get(\"repo\"), str)\n        and \"/\" in source[\"repo\"]\n    )\n\nassert valid_github_source(payload[\"source\"]), \"github source needs 'repo' ('org/repo')\"","typeGuard":"type GitHubSource = { source: 'github'; repo: string };\nfunction isGitHubSource(s: unknown): s is GitHubSource {\n  return (\n    !!s && typeof s === 'object' &&\n    (s as any).source === 'github' &&\n    typeof (s as any).repo === 'string' && (s as any).repo.includes('/')\n  );\n}","tryCatchPattern":"try:\n    resp = client.post(f\"{base}/claude-code/plugins\", json=payload)\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and \"'repo' field\" in e.response.text:\n        raise ValueError(\"add source.repo = 'org/repo' to the github source\") from e\n    raise","preventionTips":["Build plugin registration payloads from a typed model/Pydantic schema that requires repo for github sources.","Keep one canonical JSON example per source type in the repo and lint payloads against it in CI."],"tags":["litellm-proxy","claude-code-marketplace","validation","http-400","plugin-registration"],"backgroundTag":"request-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}