ytdl-org/youtube-dl · error · ExtractorError

YouTube said: %s

Error message

YouTube said: %s

What it means

Raised by _extract_and_report_alerts when the YouTube response's alerts array contains an alert of type 'error' (and fatal=True): the last error alert's message becomes 'YouTube said: <message>'. Non-error alerts and 'Unavailable videos will be hidden during playback' are only warned. expected comes from the caller, so playlist-level extraction usually treats it as an expected, user-visible condition.

Source

Thrown at youtube_dl/extractor/youtube.py:4038

                    continue
                message = self._get_text(alert_dict, 'text')
                if message:
                    yield alert_type, message

        errors, warnings = [], []
        _IGNORED_WARNINGS = T('Unavailable videos will be hidden during playback')
        for alert_type, alert_message in alerts():
            if alert_type.lower() == 'error' and fatal:
                errors.append([alert_type, alert_message])
            elif alert_message not in _IGNORED_WARNINGS:
                warnings.append([alert_type, alert_message])

        for alert_type, alert_message in itertools.chain(warnings, errors[:-1]):
            self.report_warning(
                'YouTube said: %s - %s' % (alert_type, alert_message),
                only_once=only_once)
        if errors:
            raise ExtractorError(
                'YouTube said: %s' % (errors[-1][1],), expected=expected)

    def _extract_from_tabs(self, item_id, webpage, data, tabs):
        selected_tab = self._extract_selected_tab(tabs)
        renderer = traverse_obj(data, ('metadata', 'channelMetadataRenderer'),
                                expected_type=dict) or {}
        playlist_id = item_id
        title = description = None
        if renderer:
            channel_title = txt_or_none(renderer.get('title')) or item_id
            tab_title = txt_or_none(selected_tab.get('title'))
            title = join_nonempty(
                channel_title or item_id, tab_title,
                txt_or_none(selected_tab.get('expandedText')),
                delim=' - ')
            description = txt_or_none(renderer.get('description'))
            playlist_id = txt_or_none(renderer.get('externalId')) or playlist_id
        else:

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Act on the alert message text — it is YouTube's own statement about the playlist/channel item (removed, private, nonexistent).
  2. Verify the URL opens in a browser; nonexistent or private playlists legitimately error.
  3. Update youtube-dl if alert handling or the surrounding tab extraction regressed.
  4. For batch crawling, catch this per-playlist and continue with other items.
Defensive patterns

Strategy: try-catch

Validate before calling

def has_error_alerts(data: dict) -> bool:
    return any(a.get('alertRenderer', {}).get('type', '').lower() == 'error'
               for a in (data.get('alerts') or []))

Try / catch

try:
    entries = ydl.extract_info(playlist_url)
except ExtractorError as e:
    if str(e).startswith('YouTube said:'):
        skip_playlist(playlist_url)  # upstream error alert; read message text
    else:
        raise

Prevention

When it happens

Trigger: Any channel/playlist/mix extraction whose API response includes an error alert — e.g. 'This video is unavailable', 'The playlist does not exist', 'This video is private' inside playlist continuation payloads.

Common situations: Dead/private playlist or channel URLs; playlists where YouTube injects error alerts for removed entries; mix/radio playlists that expire; region-dependent availability alerts.

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/896241a92e992f0d. Report an issue: GitHub.