{"record":{"id":"29cf07869874dd8d","repo":"BerriAI/litellm","slug":"git-subdir-source-must-include-path-field-e-g","errorCode":null,"errorMessage":"git-subdir source must include 'path' field (e.g., 'plugins/plugin-name')","messagePattern":"git-subdir source must include 'path' field \\(e\\.g\\., 'plugins/plugin-name'\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py","lineNumber":190,"sourceCode":"                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:\n        raise HTTPException(\n            status_code=400,\n            detail={\"error\": \"source.source must be 'github', 'url', or 'git-subdir'\"},\n        )\n\n\ndef _build_plugin_manifest(name: str, spec: PluginSpec) -> Mapping[str, object]:","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py#L172-L208","documentation":"400 thrown by _validate_plugin_source() for a git-subdir source that has a url but no \"path\" key. LiteLLM needs the subdirectory inside the repository where the plugin lives; without it the source would be equivalent to a plain url source.","triggerScenarios":"POST /claude-code/plugins or PUT /claude-code/plugins/{name} with {\"source\": {\"source\": \"git-subdir\", \"url\": \"https://github.com/org/monorepo.git\"}} — path key missing. The check is truthiness-based, so \"path\": \"\" also fails.","commonSituations":"Registering a monorepo plugin and forgetting the directory; using a key like \"dir\", \"subdir\", or \"subpath\" instead of 'path'; path built from a variable that is None/empty at runtime.","solutions":["Add the path field with the plugin's directory relative to the repo root: \"path\": \"plugins/plugin-name\".","Use exactly the key 'path'; then make sure its value passes the segment/segment regex (no leading '/', no '..' segments, no backslashes).","If the plugin actually lives at the repo root, use source type \"url\" instead of git-subdir."],"exampleFix":"# before\n{\"source\": {\"source\": \"git-subdir\", \"url\": \"https://github.com/org/monorepo.git\"}}\n\n# after\n{\"source\": {\"source\": \"git-subdir\", \"url\": \"https://github.com/org/monorepo.git\", \"path\": \"plugins/plugin-name\"}}","handlingStrategy":"validation","validationCode":"source = payload[\"source\"]\nif source.get(\"source\") == \"git-subdir\" and not source.get(\"path\"):\n    raise ValueError(\"git-subdir source requires a non-empty 'path' (e.g. 'plugins/plugin-name')\")","typeGuard":"function hasSubdirPath(s: unknown): boolean {\n  const o = s as Record<string, unknown>;\n  return typeof o?.path === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]*(\\/[a-zA-Z0-9][a-zA-Z0-9._-]*)*$/.test(o.path);\n}","tryCatchPattern":null,"preventionTips":["Compute path from the repo layout (never free-typed) and assert it is a non-empty relative path.","CI-lint plugin manifests so a git-subdir entry without path fails before it reaches the proxy."],"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"}