fix(claude-code): bound concurrent GitHub fetches during marketplace dir-scan

Unbounded asyncio.gather fan-out during skills-dir discovery (one HTTP
fetch per skill folder) serialized enough blocking socket.getaddrinfo()
calls inside the shared SSRF validator to blow through the sync timeout
for repos with more than a handful of skills. Import would report
sync_status "error" and only succeed after a manual re-sync. Bounding
concurrency to 6 in-flight fetches fixes the timeout without touching
the shared SSRF utility.
This commit is contained in:
Krrish Dholakia 2026-07-10 20:51:10 -07:00
parent 106b974e05
commit 969cf79dba

View file

@ -47,6 +47,15 @@ from litellm.types.proxy.claude_code_endpoints import (
DEFAULT_SYNC_TIMEOUT_SECONDS = 10.0
# validate_url() (litellm_core_utils/url_utils.py) does a blocking
# socket.getaddrinfo() per request; discovery fans out one fetch per skill
# folder via asyncio.gather, and unbounded concurrency there serializes enough
# DNS lookups on the event loop to blow through DEFAULT_SYNC_TIMEOUT_SECONDS
# for repos with more than a handful of skills. Bounding concurrency here
# keeps each individual fetch fast without touching the shared SSRF utility.
_MAX_CONCURRENT_GITHUB_FETCHES = 6
_github_fetch_semaphore = asyncio.Semaphore(_MAX_CONCURRENT_GITHUB_FETCHES)
SourceHost = Literal["github", "gitlab", "bitbucket", "url"]
SyncErrorReason = Literal["unreachable", "http_error", "invalid_json", "invalid_schema"]
@ -163,7 +172,9 @@ async def _http_get(client: AsyncHTTPHandler, url: str, *, timeout: float) -> ht
try:
# async_safe_get is SSRF-guarded (resolves + validates every redirect
# hop) - required here because the target URL is admin-supplied.
return await async_safe_get(client, url, headers={}, timeout=timeout)
# Bounded by _github_fetch_semaphore: see its module-level comment.
async with _github_fetch_semaphore:
return await async_safe_get(client, url, headers={}, timeout=timeout)
except SSRFError as exc:
raise MarketplaceSyncError(reason="unreachable", detail=str(exc)) from exc
except httpx.HTTPError as exc: