{"record":{"id":"0947d6f00a0e869c","repo":"unslothai/unsloth","slug":"youtube-returned-an-unreadable-response","errorCode":null,"errorMessage":"YouTube returned an unreadable response.","messagePattern":"YouTube returned an unreadable response\\.","errorType":"exception","errorClass":"TranscriptUnavailable","httpStatus":null,"severity":"error","filePath":"studio/backend/core/youtube_transcript.py","lineNumber":188,"sourceCode":"                    \"osName\": \"Android\",\n                    \"osVersion\": \"11\",\n                    \"hl\": \"en\",\n                    \"gl\": \"US\",\n                },\n            },\n            \"videoId\": video_id,\n            \"contentCheckOk\": True,\n            \"racyCheckOk\": True,\n        },\n    ) as response:\n        response.raise_for_status()\n        body = await _read_capped(\n            response, _MAX_PLAYER_BYTES, \"YouTube returned an unexpectedly large response.\"\n        )\n    try:\n        player = json.loads(body.decode(\"utf-8\"))\n    except (UnicodeDecodeError, ValueError) as error:\n        raise TranscriptUnavailable(\"YouTube returned an unreadable response.\") from error\n    if not isinstance(player, dict):\n        raise TranscriptUnavailable(\"YouTube returned an unreadable response.\")\n    return player\n\n\ndef _select_track(\n    tracks: list[dict[str, Any]], tracklist: dict[str, Any], languages: Sequence[str]\n) -> dict[str, Any]:\n    for language in languages:\n        wanted = str(language).strip().lower()\n        if not wanted:\n            continue\n        base = wanted.split(\"-\")[0]\n        for want_generated in (False, True):\n            candidates = [t for t in tracks if (t.get(\"kind\") == \"asr\") is want_generated]\n            # exact locale before the base-language fallback: a pt-BR request must not\n            # take a pt-PT track just because it is listed first\n            for matches_wanted in (","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/youtube_transcript.py#L170-L206","documentation":"TranscriptUnavailable raised by _fetch_player when the innertube player response body cannot be decoded as UTF-8 or parsed as JSON (UnicodeDecodeError or ValueError from json.loads, chained via `from error`). HTTP status was OK and the body was under the size cap, but the content is not a JSON document.","triggerScenarios":"YouTube (or an intercepting proxy) returns an HTML consent page, captcha, or error page with HTTP 200; a truncated or gzip-mangled body; a captive portal injecting content. Any of these make json.loads fail and hit this branch.","commonSituations":"Running transcript fetches from datacenter IPs that get soft-blocked into HTML interstitials; corporate proxies rewriting responses; transient CDN corruption.","solutions":["Retry the request after a delay or from a different network/IP.","Inspect the raw response (log body[:200]) to identify consent/captcha pages; if consent-related, the request headers/client config need adjusting or cookies must be supplied.","Rate-limit your fetching to avoid being served block pages."],"exampleFix":"# before\nplayer = await _fetch_player(client, video_id)  # raises on HTML body\n\n# after\nfor attempt in range(3):\n    try:\n        player = await _fetch_player(client, video_id)\n        break\n    except TranscriptUnavailable:\n        if attempt == 2:\n            raise\n        await asyncio.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        player_or_transcript = await fetch_transcript(video_id)\n        break\n    except TranscriptUnavailable as e:\n        if \"unreadable response\" in str(e) and attempt < 2:\n            await asyncio.sleep(2 ** attempt)\n            continue\n        raise","preventionTips":["Rate-limit transcript requests to avoid HTML interstitial/bot pages.","Log a body snippet when this fires to distinguish consent pages from corruption.","Run fetchers from residential rather than datacenter egress when possible."],"tags":["youtube","transcript","json-parse","network"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}