microsoft/markitdown · error · Exception
Operation failed after {retries} attempts.
Error message
Operation failed after {retries} attempts. What it means
YoutubeConverter._retry_operation() retries a callable up to `retries` times with `delay` seconds between attempts (printing each failure to stdout). When all attempts exhaust, it raises a bare Exception whose message only states the count — the original exception is neither chained (`from e`) nor included, so the root cause (network error, HTTP 470, extraction failure) is lost. Code that calls it (e.g. transcript or format fetching) surfaces only 'Operation failed after 3 attempts.'
Source
Thrown at packages/markitdown/src/markitdown/converters/_youtube_converter.py:238
if k == key:
return json[k]
if result := self._findKey(v, key):
return result
return None
def _retry_operation(self, operation, retries=3, delay=2):
"""Retries the operation if it fails."""
attempt = 0
while attempt < retries:
try:
return operation() # Attempt the operation
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < retries - 1:
time.sleep(delay) # Wait before retrying
attempt += 1
# If all attempts fail, raise the last exception
raise Exception(f"Operation failed after {retries} attempts.")
View on GitHub (pinned to fd239d5d2b)
Solutions
- Check the printed stdout lines ('Attempt N failed: ...') above the traceback — they contain the actual underlying error message
- For YouTube blocking server IPs, route requests through a residential proxy or run from a network YouTube does not flag
- Slow down between conversions (sleep/backoff between URLs) to avoid rate limiting
- Verify the video is public, not age-restricted, and available in your region by opening the URL directly
- As a library fix, chain the cause: `raise Exception(...) from e` so the traceback shows the root error
Example fix
# before (in _retry_operation)
raise Exception(f"Operation failed after {retries} attempts.")
# after: preserve and chain the last cause
raise Exception(f"Operation failed after {retries} attempts.") from last_exception Defensive patterns
Strategy: retry
Validate before calling
import urllib.request, json
def youtube_video_available(video_id: str) -> bool:
try:
with urllib.request.urlopen(f"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v={video_id}&format=json", timeout=10) as r:
return r.status == 200
except Exception:
return False Try / catch
import time
from markitdown import MarkItDown
for attempt in range(3):
try:
result = MarkItDown().convert("https://www.youtube.com/watch?v=VIDEO_ID")
break
except Exception as e:
# message is generic ('Operation failed after N attempts.'); real cause is on stdout
if attempt == 2:
logger.error("youtube conversion failed; check 'Attempt N failed' stdout lines")
raise
time.sleep(2 ** attempt) Prevention
- Capture stdout/stderr of the process: _retry_operation prints the real per-attempt errors there
- Rate-limit and add jittered backoff between YouTube conversions to avoid bot detection
- Pre-validate URLs with the oembed endpoint to skip deleted/private/region-locked videos before conversion
- Run YouTube conversions from residential IPs or a rotating proxy when server IPs get blocked
When it happens
Trigger: Converting a YouTube URL while youtube-transcript-api or yt-dlp operations fail every attempt: network blocks, YouTube rate-limiting/bot detection, age- or region-restricted videos, or invalid video IDs. Any transient-or-permanent failure repeated `retries` times (default 3) triggers this final generic Exception.
Common situations: Server IPs (cloud/CI) blocked by YouTube causing persistent failures; rate limits when converting many URLs in a loop; deleted/private videos; corporate proxies intercepting requests; the generic message making debugging hard because the printed stdout line is the only clue to the real error.
AI-assisted analysis of microsoft/markitdown@fd239d5d2b (2026-08-14).
Data as JSON: /api/errors/861c50073f014320.
Report an issue: GitHub.