yt-dlp/yt-dlp · error · ExtractorError

Unable to solve JS challenge

Error message

Unable to solve JS challenge

What it means

Raised when TikTok's WAF proof-of-work cannot be completed: the brute-force loop tries up to 1,000,000 SHA-256 candidates against the expected digest and finds no match. That means the challenge parameters no longer correspond to the algorithm yt-dlp implements (digest computed differently or encoded another way).

Source

Thrown at yt_dlp/extractor/tiktok.py:251

        self.to_screen('Solving JS challenge using native Python implementation')

        expected_digest = traverse_obj(challenge_data, (
            'v', 'c', {str}, {base64.b64decode},
            {require('challenge expected digest')}))

        base_hash = traverse_obj(challenge_data, (
            'v', 'a', {str}, {base64.b64decode},
            {hashlib.sha256}, {require('challenge base hash')}))

        for i in range(1_000_001):
            number = str(i).encode()
            test_hash = base_hash.copy()
            test_hash.update(number)
            if test_hash.digest() == expected_digest:
                challenge_data['d'] = base64.b64encode(number).decode()
                break
        else:
            raise ExtractorError('Unable to solve JS challenge')

        wci_cookie_value = base64.b64encode(
            json.dumps(challenge_data, separators=(',', ':')).encode()).decode()

        # At time of writing, the wci cookie name was `_wafchallengeid`
        wci_cookie_name = traverse_obj(webpage, (
            {find_element(id='wci', html=True)}, {extract_attributes},
            'class', {require('challenge cookie name')}))

        # At time of writing, the **optional** rci cookie name was `waforiginalreid`
        rci_cookie_name = traverse_obj(webpage, (
            {find_element(id='rci', html=True)}, {extract_attributes}, 'class'))
        rci_cookie_value = traverse_obj(webpage, (
            {find_element(id='rs', html=True)}, {extract_attributes}, 'class'))

        # Actual JS sets Max-Age=1 for the cookies, but we'll manually clear them later instead
        expire_time = int(time.time()) + (self.get_param('sleep_interval_requests') or 0) + 120
        self._set_cookie('.tiktok.com', wci_cookie_name, wci_cookie_value, expire_time=expire_time)

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Update yt-dlp to pick up an adjusted solver
  2. Retry — malformed challenges are sometimes served transiently
  3. Use cookies from a browser that already passed the challenge
  4. Report persistent occurrences upstream with -v output
Defensive patterns

Strategy: retry

Type guard

def is_challenge_solver_failure(exc: Exception) -> bool:
    return isinstance(exc, ExtractorError) and 'solve JS challenge' in str(exc)

Try / catch

try:
    info = ydl.extract_info(url, download=False)
except ExtractorError as e:
    if 'solve JS challenge' in str(e):
        # algorithm mismatch: only an update or cookies can fix it
        upgrade_yt_dlp()
        return retry_with_cookies_if_still_failing(url)
    raise

Prevention

When it happens

Trigger: TikTok changes the challenge encoding or hashing scheme so the implemented formula searches the wrong space; occasionally a malformed challenge payload yields an impossible digest.

Common situations: Anti-bot algorithm updates between yt-dlp releases; corrupted challenge JSON from truncated responses.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/d3692747991f6250. Report an issue: GitHub.