{"record":{"id":"28e3b2276d3e3268","repo":"BerriAI/litellm","slug":"git-subdir-source-must-include-url-field-e-g","errorCode":null,"errorMessage":"git-subdir source must include 'url' field (e.g., 'https://github.com/org/repo.git')","messagePattern":"git-subdir source must include 'url' field \\(e\\.g\\., 'https://github\\.com/org/repo\\.git'\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py","lineNumber":183,"sourceCode":"\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\"):\n            raise HTTPException(\n                status_code=400,\n                detail={\"error\": \"git-subdir source must include 'path' field (e.g., 'plugins/plugin-name')\"},\n            )\n        if not _VALID_GIT_SUBDIR_PATH_RE.match(source[\"path\"]):\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": \"git-subdir 'path' must be a relative path of the form 'segment/segment' (alphanumeric, dots, hyphens, underscores only)\"\n                },\n            )\n    else:","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py#L165-L201","documentation":"400 thrown by _validate_plugin_source() for a git-subdir plugin source that lacks a truthy \"url\" value. git-subdir sources point at a subdirectory of a git repository, so LiteLLM requires both the repository url and the relative path; this error fires on the missing/falsy url check (note it uses `if not source.get(\"url\")`, so an empty string also triggers it).","triggerScenarios":"POST /claude-code/plugins or PUT /claude-code/plugins/{name} with {\"source\": {\"source\": \"git-subdir\", \"path\": \"plugins/my-plugin\"}} — url absent — or with \"url\": \"\" (empty string), which fails the truthiness check.","commonSituations":"Splitting a monorepo plugin into git-subdir form and forgetting the repo URL; templating code that renders an empty url when the env/config variable is unset; assuming the path alone identifies the plugin.","solutions":["Include a non-empty repository url alongside the path: \"source\": {\"source\": \"git-subdir\", \"url\": \"https://github.com/org/monorepo.git\", \"path\": \"plugins/my-plugin\"}.","Check that the url value is not an empty string — unlike the github/url checks, this branch rejects falsy values, not just missing keys.","Verify the 'path' field is also present and matches the segment/segment allowlist, otherwise you will get the follow-up path validation error."],"exampleFix":"# before\n{\"source\": {\"source\": \"git-subdir\", \"path\": \"plugins/my-plugin\"}}\n\n# after\n{\"source\": {\"source\": \"git-subdir\", \"url\": \"https://github.com/org/monorepo.git\", \"path\": \"plugins/my-plugin\"}}","handlingStrategy":"validation","validationCode":"source = payload[\"source\"]\nif source.get(\"source\") == \"git-subdir\":\n    if not source.get(\"url\"):\n        raise ValueError(\"git-subdir source requires a non-empty 'url'\")\n    if not source.get(\"path\"):\n        raise ValueError(\"git-subdir source requires 'path'\")","typeGuard":"type GitSubdirSource = { source: 'git-subdir'; url: string; path: string };\nfunction isGitSubdirSource(s: unknown): s is GitSubdirSource {\n  const o = s as Record<string, unknown>;\n  return (\n    !!o && o.source === 'git-subdir' &&\n    typeof o.url === 'string' && o.url.length > 0 &&\n    typeof o.path === 'string' && o.path.length > 0\n  );\n}","tryCatchPattern":null,"preventionTips":["Treat git-subdir as a 3-field contract (source/url/path) and assert all three client-side.","Reject empty-string values, not just missing keys — the server truthiness-checks url for this type."],"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"}