{"record":{"id":"0337e4805cd38b0b","repo":"unslothai/unsloth","slug":"youtube-redirected-the-caption-request-too-many-ti","errorCode":null,"errorMessage":"YouTube redirected the caption request too many times.","messagePattern":"YouTube redirected the caption request too many times\\.","errorType":"exception","errorClass":"TranscriptUnavailable","httpStatus":null,"severity":"error","filePath":"studio/backend/core/youtube_transcript.py","lineNumber":276,"sourceCode":"    body = b\"\"\n    for _ in range(_MAX_CAPTION_REDIRECTS + 1):\n        # Redirects are followed by hand so the host allowlist covers every hop, not\n        # just the URL the player response handed us.\n        async with client.stream(\n            \"GET\", url, headers = {\"User-Agent\": _USER_AGENT}, follow_redirects = False\n        ) as response:\n            location = response.headers.get(\"location\")\n            if response.is_redirect and location:\n                url = urljoin(url, location)\n                _validated_caption_url(url)\n                continue\n            response.raise_for_status()\n            body = await _read_capped(\n                response, _MAX_CAPTION_BYTES, \"This video's captions are too large to attach.\"\n            )\n        break\n    else:\n        raise TranscriptUnavailable(\"YouTube redirected the caption request too many times.\")\n    if not body:\n        raise TranscriptUnavailable(\"YouTube returned no caption text for this video.\")\n\n    try:\n        payload = json.loads(body.decode(\"utf-8\"))\n    except (UnicodeDecodeError, ValueError) as error:\n        raise TranscriptUnavailable(\"YouTube returned unreadable caption data.\") from error\n    events = payload.get(\"events\") if isinstance(payload, dict) else None\n    return _flatten_events(events or [])\n\n\ndef _flatten_events(events: list[Any]) -> str:\n    lines: list[str] = []\n    for event in events:\n        if not isinstance(event, dict):\n            continue\n        # aAppend cues carry only the rolling-window newline between ASR lines.\n        if event.get(\"aAppend\") == 1:","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/youtube_transcript.py#L258-L294","documentation":"TranscriptUnavailable raised in _fetch_track_text when the manual redirect loop exhausts all iterations without getting a non-redirect response. The code follows redirects itself (validating each hop via _validated_caption_url) up to a fixed maximum; exceeding that budget triggers this instead of an infinite redirect loop.","triggerScenarios":"YouTube's caption endpoint replying with a redirect chain longer than the loop budget (e.g. repeated 302 hops between consent and caption URLs); a misbehaving or adversarial server bouncing the request between allowed hosts indefinitely.","commonSituations":"Consent-cookie redirect loops when no consent cookies are sent; CDN misconfiguration loops; extremely rare in normal operation.","solutions":["Retry once — transient redirect storms usually resolve.","Ensure requests carry standard browser-like headers/cookies so YouTube does not loop through consent redirects.","If persistent, capture the redirect chain (log each url) and report; the loop budget or cookie handling may need a fix in the client."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    transcript = await fetch_transcript(video_id)\nexcept TranscriptUnavailable as e:\n    if \"redirected the caption request too many times\" in str(e):\n        await asyncio.sleep(1.0)\n        transcript = await fetch_transcript(video_id)\n    else:\n        raise","preventionTips":["Send browser-like headers/cookies so consent redirects resolve in one hop.","Retry once — redirect loops are almost always transient.","If loops persist, log the chain of URLs to identify the consent endpoint involved."],"tags":["youtube","transcript","redirects","network"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}