ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

Raised by the Linux Academy extractor when the login endpoint returns HTTP 401: the JSON error body's 'description' (or 'code') becomes the message alongside the extractor name. It is expected=True and means the login request itself was rejected — bad credentials or an expired auth flow.

Source

Thrown at youtube_dl/extractor/linuxacademy.py:126

            'sso': 'true',
        })

        login_state_url = urlh.geturl()

        try:
            login_page = self._download_webpage(
                'https://login.linuxacademy.com/usernamepassword/login', None,
                'Downloading login page', data=json.dumps(login_data).encode(),
                headers={
                    'Content-Type': 'application/json',
                    'Origin': 'https://login.linuxacademy.com',
                    'Referer': login_state_url,
                })
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                error = self._parse_json(e.cause.read(), None)
                message = error.get('description') or error['code']
                raise ExtractorError(
                    '%s said: %s' % (self.IE_NAME, message), expected=True)
            raise

        callback_page, urlh = self._download_webpage_handle(
            'https://login.linuxacademy.com/login/callback', None,
            'Downloading callback page',
            data=urlencode_postdata(self._hidden_inputs(login_page)),
            headers={
                'Content-Type': 'application/x-www-form-urlencoded',
                'Origin': 'https://login.linuxacademy.com',
                'Referer': login_state_url,
            })

        access_token = self._search_regex(
            r'access_token=([^=&]+)', urlh.geturl(),
            'access token', default=None)
        if not access_token:
            access_token = self._parse_json(

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Verify credentials in a browser first
  2. Update youtube-dl / switch to yt-dlp — the login handshake is version-sensitive
  3. If MFA blocks headless login, use --cookies with an exported authenticated session
  4. Read the embedded 'description' text; it usually names the exact refusal reason
Defensive patterns

Strategy: fallback

Try / catch

try:
    ydl.download([url])
except ExtractorError as e:
    if ' said: ' in str(e):
        # login rejected (401): fall back to exported session cookies
        switch_to_cookie_auth_and_retry(url)
    else:
        raise

Prevention

When it happens

Trigger: Using --username/--password with linuxacademy.com URLs where POST https://login.linuxacademy.com/usernamepassword/login responds 401 with a JSON error — wrong credentials, stale login state url, or the auth flow (state/nonce handshake) having changed.

Common situations: Incorrect email/password; Linux Academy (now Cloud Assessment/TAC Security) changing their auth stack so the two-step login dance breaks; MFA required on the account; old youtube-dl versions predating an auth-flow update.

Related errors


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