{"record":{"id":"170e98aa5e0c28c9","repo":"BerriAI/litellm","slug":"url-source-must-include-url-field-e-g-https","errorCode":null,"errorMessage":"URL source must include 'url' field (e.g., 'https://github.com/org/repo.git')","messagePattern":"URL 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":177,"sourceCode":"# 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\"):\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(","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py#L159-L195","documentation":"400 thrown by _validate_plugin_source() when the plugin source object declares \"source\": \"url\" but contains no \"url\" key. For the url source type LiteLLM needs the full clone URL (e.g. https://github.com/org/repo.git); the type tag alone carries no location information.","triggerScenarios":"POST /claude-code/plugins or PUT /claude-code/plugins/{name} with {\"source\": {\"source\": \"url\"}} — the url key missing, empty, or misspelled (e.g. \"uri\", \"git_url\", \"clone_url\").","commonSituations":"Copy-pasting a registration payload and deleting the url line while keeping the type; renaming fields to match an internal convention; code that reads the URL from an env var that is unset, producing a dict without the key.","solutions":["Add the url field with a full git URL: \"source\": {\"source\": \"url\", \"url\": \"https://github.com/org/repo.git\"}.","Verify the key name is exactly 'url' and the value is a non-empty string (note: for url sources the key presence is checked, for git-subdir the value is truthiness-checked).","If you only know the GitHub org/repo, use \"source\": \"github\" with a \"repo\" field instead."],"exampleFix":"# before\n{\"name\": \"my-plugin\", \"source\": {\"source\": \"url\"}}\n\n# after\n{\"name\": \"my-plugin\", \"source\": {\"source\": \"url\", \"url\": \"https://github.com/org/repo.git\"}}","handlingStrategy":"validation","validationCode":"source = payload[\"source\"]\nif source.get(\"source\") == \"url\" and not source.get(\"url\"):\n    raise ValueError(\"url source requires a non-empty 'url' field\")","typeGuard":"type UrlSource = { source: 'url'; url: string };\nfunction isUrlSource(s: unknown): s is UrlSource {\n  return (\n    !!s && typeof s === 'object' &&\n    (s as any).source === 'url' &&\n    typeof (s as any).url === 'string' && (s as any).url.length > 0\n  );\n}","tryCatchPattern":null,"preventionTips":["Validate that source.url is a non-empty absolute https URL before sending.","Derive the payload from config that fails loudly when the URL variable is unset (no silent empty strings)."],"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"}