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
- Update yt-dlp to pick up an adjusted solver
- Retry — malformed challenges are sometimes served transiently
- Use cookies from a browser that already passed the challenge
- 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
- Update yt-dlp first — solver changes are release-blockers and land fast
- Reuse browser cookies to avoid solving challenges altogether
- Do not write your own solver fork without upstream coordination
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
- No working app info is available
- Unable to extract challenge data
- Unexpected response from webpage request
- Unable to extract universal data for rehydration
- You have exceeded the rate limit. Try again later
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/d3692747991f6250.
Report an issue: GitHub.