{"record":{"id":"3582a3b98bb2aac0","repo":"crewAIInc/crewAI","slug":"unable-to-extract-transcript-from-youtube-video-v","errorCode":null,"errorMessage":"Unable to extract transcript from YouTube video {video_id}: {e!s}","messagePattern":"Unable to extract transcript from YouTube video (.+?): (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/rag/loaders/youtube_video_loader.py","lineNumber":94,"sourceCode":"                    yt = YouTube(video_url)\n                    metadata[\"title\"] = yt.title\n                    metadata[\"author\"] = yt.author\n                    metadata[\"length_seconds\"] = yt.length\n                    metadata[\"description\"] = (\n                        yt.description[:500] if yt.description else None\n                    )\n\n                    if yt.title:\n                        content = f\"Title: {yt.title}\\n\\nAuthor: {yt.author or 'Unknown'}\\n\\nTranscript:\\n{content}\"\n                except Exception:  # noqa: S110\n                    pass\n            else:\n                raise ValueError(\n                    f\"No transcript available for YouTube video: {video_id}\"\n                )\n\n        except Exception as e:\n            raise ValueError(\n                f\"Unable to extract transcript from YouTube video {video_id}: {e!s}\"\n            ) from e\n\n        return LoaderResult(\n            content=content,\n            source=video_url,\n            metadata=metadata,\n            doc_id=self.generate_doc_id(source_ref=video_url, content=content),\n        )\n\n    @staticmethod\n    def _extract_video_id(url: str) -> str | None:\n        \"\"\"Extract video ID from various YouTube URL formats.\"\"\"\n        patterns = [\n            r\"(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/|youtube\\.com\\/v\\/)([^&\\n?#]+)\",\n        ]\n\n        for pattern in patterns:","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/rag/loaders/youtube_video_loader.py#L76-L112","documentation":"The video loader's outer catch-all ValueError wrapping every exception during transcript retrieval and metadata enrichment. Because it uses 'except Exception', it also swallows and re-wraps the loader's own 'No transcript available' raise (254) — check the message suffix or __cause__ to distinguish missing captions from genuine failures. Inner metadata enrichment failures (title/author via pytube) are deliberately ignored with a bare pass, so this error almost always comes from the transcript API call itself.","triggerScenarios":"youtube-transcript-api raising network errors, rate limiting / IP blocks from YouTube (common on datacenter IPs, 'requests to youtube... blocked' errors), or API version incompatibilities after youtube-transcript-api's 1.x redesign (e.g. YouTubeTranscriptApi() constructor vs old static .list usage). The wrapped 'No transcript available' case is the other major trigger.","commonSituations":"Server-side scraping at volume triggering YouTube IP blocks; upgrading youtube-transcript-api past a major version where the internal API changed; running from cloud VMs whose IP ranges are challenged; transient network outages.","solutions":["Inspect e.__cause__ / the message suffix to classify: 'No transcript available' = content issue (see 254); 'blocked' / RequestBlocked = IP rate-limit; other = network or version issue.","Upgrade or pin youtube-transcript-api to a version compatible with the loader's usage: uv add 'youtube-transcript-api>=1.0'.","If blocked, route requests through a proxy the transcript API supports, or reduce request frequency / add caching.","Add retry with backoff for transient network errors before surfacing the failure."],"exampleFix":"# before\ntry:\n    result = loader.load(src)\nexcept ValueError as e:\n    raise  # loses classification\n\n# after\ntry:\n    result = loader.load(src)\nexcept ValueError as e:\n    msg = str(e)\n    if \"No transcript available\" in msg:\n        skip(src)\n    elif \"blocked\" in msg.lower():\n        backoff_and_retry(src)\n    else:\n        raise","handlingStrategy":"retry","validationCode":"def classify_yt_failure(exc: ValueError) -> str:\n    msg = str(exc)\n    if \"No transcript available\" in msg:\n        return \"no-captions\"\n    if \"blocked\" in msg.lower() or \"ip\" in msg.lower():\n        return \"rate-limited\"\n    return \"unknown\"","typeGuard":null,"tryCatchPattern":"attempt = 0\nwhile attempt < 3:\n    try:\n        result = yt_video_loader.load(src)\n        break\n    except ValueError as e:\n        kind = classify_yt_failure(e)\n        if kind == \"rate-limited\" and attempt < 2:\n            attempt += 1\n            time.sleep(2 ** attempt)\n            continue\n        if kind == \"no-captions\":\n            skip(src)\n            break\n        raise","preventionTips":["Classify the wrapped message/cause before deciding retry vs skip vs escalate.","Cache transcripts by video ID to minimize request volume and IP-block risk.","Keep youtube-transcript-api version-compatible with the loader's constructor usage."],"tags":["youtube","transcript","rate-limiting","network"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}