unslothai/unsloth · error · TranscriptUnavailable

YouTube returned a caption URL from an unexpected host.

Error message

YouTube returned a caption URL from an unexpected host.

What it means

TranscriptUnavailable raised by _validated_caption_url when a caption baseUrl (or a redirect target) is not an https URL whose hostname is in the allowlist _CAPTION_HOSTS. This is a defense so caption fetching only ever talks to trusted YouTube caption hosts, never to arbitrary hosts injected into player data.

Source

Thrown at studio/backend/core/youtube_transcript.py:234

def _default_track_index(tracks: list[dict[str, Any]], tracklist: dict[str, Any]) -> int:
    """Index of the caption track paired with the video's default audio track.

    A multi-language video lists its tracks alphabetically, so track 0 is often an
    unrelated translation rather than the language actually spoken.
    """
    audio_tracks = tracklist.get("audioTracks") or []
    audio_index = tracklist.get("defaultAudioTrackIndex")
    if isinstance(audio_index, int) and 0 <= audio_index < len(audio_tracks):
        caption_index = (audio_tracks[audio_index] or {}).get("defaultCaptionTrackIndex")
        if isinstance(caption_index, int) and 0 <= caption_index < len(tracks):
            return caption_index
    return 0


def _validated_caption_url(url: str) -> SplitResult:
    parsed = urlsplit(url)
    if parsed.scheme != "https" or (parsed.hostname or "").lower() not in _CAPTION_HOSTS:
        raise TranscriptUnavailable("YouTube returned a caption URL from an unexpected host.")
    return parsed


def _caption_url(base_url: str) -> str:
    """Ask a caption baseUrl for json3, keeping the blank-valued params YouTube sends."""
    parsed = _validated_caption_url(base_url)
    query = parse_qs(parsed.query, keep_blank_values = True)
    query["fmt"] = ["json3"]
    return urlunsplit(parsed._replace(query = urlencode(query, doseq = True)))


async def _read_capped(response: httpx.Response, limit: int, message: str) -> bytes:
    body = bytearray()
    async for chunk in response.aiter_bytes():
        body.extend(chunk)
        if len(body) > limit:
            raise TranscriptUnavailable(message)
    return bytes(body)

View on GitHub (pinned to 203007d190)

Solutions

  1. Update the _CAPTION_HOSTS allowlist in core/youtube_transcript.py with the new legitimate YouTube caption host (verify the host independently first).
  2. Verify nothing on the network path (proxy, TLS-terminating middlebox) rewrites the baseUrl.
  3. If the URL came from tampered data, discard that player response and re-fetch.

Example fix

# before
_CAPTION_HOSTS = {"www.youtube.com"}  # new host youtube.googleapis.com rejected

# after
_CAPTION_HOSTS = {"www.youtube.com", "youtube.googleapis.com", "www.googlevideo.com"}
Defensive patterns

Strategy: try-catch

Validate before calling

from urllib.parse import urlsplit

CAPTION_HOSTS = {"www.youtube.com"}  # keep in sync with core/youtube_transcript.py

def is_trusted_caption_url(url: str) -> bool:
    p = urlsplit(url)
    return p.scheme == "https" and (p.hostname or "").lower() in CAPTION_HOSTS

Type guard

def is_valid_caption_url(url: str) -> bool:
    p = urlsplit(url)
    return p.scheme == "https" and (p.hostname or "").lower() in _CAPTION_HOSTS

Try / catch

try:
    transcript = await fetch_transcript(video_id)
except TranscriptUnavailable as e:
    if "unexpected host" in str(e):
        logger.error("caption host allowlist may be stale: %s", e)
    raise

Prevention

When it happens

Trigger: A caption track's baseUrl uses http instead of https, or points at a non-allowlisted host (e.g. a CDN change like a new googlevideo subdomain not in _CAPTION_HOSTS, or tampered/proxied player data); a redirect Location header leading off-host.

Common situations: YouTube rotating caption CDN hostnames after an update (allowlist goes stale); environments with DNS/proxy manipulation rewriting URLs.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/5061d407ba3ea87f. Report an issue: GitHub.