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

  1. Check the printed stdout lines ('Attempt N failed: ...') above the traceback — they contain the actual underlying error message
  2. For YouTube blocking server IPs, route requests through a residential proxy or run from a network YouTube does not flag
  3. Slow down between conversions (sleep/backoff between URLs) to avoid rate limiting
  4. Verify the video is public, not age-restricted, and available in your region by opening the URL directly
  5. 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

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.