Add a Maintenance subsection covering why 'yt-dlp -U' fails on PyPI builds and how to update via pip, plus how to detect/remove a duplicate user+system install (the issue hit on majorhome 2026-06-16).
265 lines
8.5 KiB
Markdown
265 lines
8.5 KiB
Markdown
---
|
||
title: yt-dlp YouTube JS Challenge Fix (Fedora)
|
||
domain: troubleshooting
|
||
category: general
|
||
tags:
|
||
- yt-dlp
|
||
- fedora
|
||
- youtube
|
||
- javascript
|
||
- deno
|
||
status: published
|
||
created: 2026-04-02
|
||
updated: 2026-06-16T18:35
|
||
---
|
||
# yt-dlp YouTube JS Challenge Fix (Fedora)
|
||
|
||
## Problem
|
||
|
||
When running `yt-dlp` on Fedora, downloads may fail with the following warnings and errors:
|
||
|
||
```
|
||
WARNING: [youtube] No supported JavaScript runtime could be found.
|
||
WARNING: [youtube] [jsc:deno] Challenge solver lib script version 0.3.2 is not supported (supported version: 0.4.0)
|
||
WARNING: [youtube] 0qhgPKRzlvs: n challenge solving failed: Some formats may be missing.
|
||
ERROR: Did not get any data blocks
|
||
ERROR: fragment 1 not found, unable to continue
|
||
```
|
||
|
||
This causes subtitle downloads (and sometimes video formats) to fail silently, with the MP4 completing but subtitles being skipped.
|
||
|
||
### Root Causes
|
||
|
||
1. **No JavaScript runtime installed** — yt-dlp requires Deno or Node.js to solve YouTube's JS challenges
|
||
2. **Outdated yt-dlp** — the bundled challenge solver script is behind the required version
|
||
3. **Remote challenge solver not enabled** — the updated solver script must be explicitly fetched
|
||
|
||
---
|
||
|
||
## Fix
|
||
|
||
### 1. Install Deno
|
||
|
||
Deno is not in the Fedora repos. Install via the official installer:
|
||
|
||
```bash
|
||
curl -fsSL https://deno.land/install.sh | sh
|
||
sudo mv ~/.deno/bin/deno /usr/local/bin/deno
|
||
deno --version
|
||
```
|
||
|
||
> Alternatively, Node.js works and is available via `sudo dnf install nodejs`.
|
||
|
||
### 2. Update yt-dlp
|
||
|
||
```bash
|
||
sudo pip install -U yt-dlp --break-system-packages
|
||
```
|
||
|
||
> If installed via standalone binary: `yt-dlp -U`
|
||
|
||
### 3. Enable Remote Challenge Solver
|
||
|
||
Add `--remote-components ejs:github` to the yt-dlp command. This fetches the latest JS challenge solver from GitHub at runtime:
|
||
|
||
```bash
|
||
yt-dlp -f 'bestvideo[vcodec^=avc]+bestaudio[ext=m4a]/bestvideo+bestaudio' \
|
||
--merge-output-format mp4 \
|
||
-o "/plex/plex/%(title)s.%(ext)s" \
|
||
--write-auto-subs --embed-subs \
|
||
--remote-components ejs:github \
|
||
https://www.youtube.com/watch?v=VIDEO_ID
|
||
```
|
||
|
||
### 4. Persist the Config
|
||
|
||
Create a yt-dlp config file so `--remote-components` is applied automatically:
|
||
|
||
```bash
|
||
mkdir -p ~/.config/yt-dlp
|
||
echo '--remote-components ejs:github' > ~/.config/yt-dlp/config
|
||
```
|
||
|
||
---
|
||
|
||
## Maintenance
|
||
|
||
YouTube pushes extractor changes frequently. Keep yt-dlp current.
|
||
|
||
### Updating: the `-U` trap + avoid duplicate installs
|
||
|
||
`yt-dlp -U` **does not work** when yt-dlp was installed via pip/PyPI — the PyPI build deliberately disables the self-updater:
|
||
|
||
```
|
||
ERROR: You installed yt-dlp with pip or using the wheel from PyPi; Use that to update
|
||
```
|
||
|
||
Update through pip instead. **Pick one install method and stick to it** — running both a user install and a system install leaves two copies that drift out of sync (one updates, the other stays stale and shadows it depending on `$PATH` / sudo).
|
||
|
||
**Recommended — single user install (no sudo):**
|
||
|
||
```bash
|
||
pip3 install -U --user yt-dlp
|
||
```
|
||
|
||
This lives in `~/.local/bin/yt-dlp` and is first on a normal user's `$PATH`. Update it the same way; never use sudo.
|
||
|
||
**Alternative — system-wide (Fedora, PEP 668):**
|
||
|
||
```bash
|
||
sudo pip install -U yt-dlp --break-system-packages
|
||
```
|
||
|
||
> Only use `--break-system-packages` if you intentionally want a root-owned copy in `/usr/local`. Do **not** mix it with a `--user` install.
|
||
|
||
**Check for and remove a duplicate install:**
|
||
|
||
```bash
|
||
which -a yt-dlp # more than one path = duplicate installs
|
||
sudo pip3 uninstall -y yt-dlp # removes the /usr/local (system) copy + its wrapper
|
||
```
|
||
|
||
> If installed via the standalone binary (not pip), `yt-dlp -U` is the correct updater.
|
||
|
||
---
|
||
|
||
## Known Limitations
|
||
|
||
### n-Challenge Failure: "found 0 n function possibilities"
|
||
|
||
Even with Deno installed, the remote solver downloaded, and yt-dlp up to date, some YouTube player versions can still fail n-challenge solving:
|
||
|
||
```
|
||
WARNING: [youtube] [jsc] Error solving n challenge request using "deno" provider:
|
||
Error running deno process (returncode: 1): error: Uncaught (in promise)
|
||
"found 0 n function possibilities".
|
||
WARNING: [youtube] n challenge solving failed: Some formats may be missing.
|
||
ERROR: [youtube] Requested format is not available.
|
||
```
|
||
|
||
This is a known upstream issue tied to specific YouTube player builds (e.g. `e42f4bf8`). It is not fixable locally — it requires a yt-dlp patch when YouTube rotates the player.
|
||
|
||
**Workaround:** Use a permissive format fallback instead of forcing AVC:
|
||
|
||
```bash
|
||
yt-dlp -f 'bestvideo+bestaudio/best' \
|
||
--merge-output-format mp4 \
|
||
-o "/plex/plex/%(title)s.%(ext)s" \
|
||
--write-auto-subs --embed-subs \
|
||
--remote-components ejs:github \
|
||
https://www.youtube.com/watch?v=VIDEO_ID
|
||
```
|
||
|
||
This lets yt-dlp pick the best available format rather than failing on a missing AVC stream. To inspect what formats are actually available:
|
||
|
||
```bash
|
||
yt-dlp --list-formats --remote-components ejs:github \
|
||
https://www.youtube.com/watch?v=VIDEO_ID
|
||
```
|
||
|
||
### HTTP 429 Too Many Requests + Impersonation Warning
|
||
|
||
Downloads or subtitle fetches fail with:
|
||
|
||
```
|
||
WARNING: The extractor specified to use impersonation for this download,
|
||
but no impersonate target is available.
|
||
ERROR: Unable to download video subtitles for 'en-en-US': HTTP Error 429: Too Many Requests
|
||
```
|
||
|
||
**Cause:** yt-dlp needs `curl_cffi` to impersonate a real browser's TLS fingerprint. Without it, YouTube detects the non-browser client and rate-limits with 429s. Subtitle downloads are usually the first to fail (YouTube's `timedtext` endpoint has its own, stricter per-IP bucket).
|
||
|
||
**Fix (pin `curl_cffi` to the supported range):**
|
||
|
||
```bash
|
||
pip3 install --user -U "curl_cffi>=0.10,<0.15" "yt-dlp-ejs>=0.8"
|
||
```
|
||
|
||
> ⚠️ Do **not** run a bare `pip install -U curl_cffi`. As of yt-dlp **2026.03.17**, the backend in `yt_dlp/networking/_curlcffi.py` hard-caps at `0.14.x`:
|
||
>
|
||
> ```
|
||
> ImportError: Only curl_cffi versions 0.5.10 and 0.10.x through 0.14.x are supported
|
||
> ```
|
||
>
|
||
> Installing `curl_cffi 0.15.0` silently disables impersonation — `yt-dlp --list-impersonate-targets` will show every source as `(unavailable)` even though `import curl_cffi` works fine. Always pin to `<0.15` until yt-dlp widens the range.
|
||
|
||
**Verify:**
|
||
|
||
```bash
|
||
yt-dlp --list-impersonate-targets | head -5
|
||
```
|
||
|
||
Should show real entries (`Chrome-133 Macos-15 curl_cffi`), not the `(unavailable)` table.
|
||
|
||
**If the 429 persists on subtitles only:** the `timedtext` bucket is already hot from prior retries. Either wait 15–60 min, skip subs for this download (`--no-write-subs --no-write-auto-subs`), or throttle with `--sleep-subtitles 5` on retry. The video/audio path is not affected.
|
||
|
||
**Companion gotcha — `yt-dlp-ejs` version drift:** if `yt-dlp -U` reports yt-dlp is current but you still see:
|
||
|
||
```
|
||
WARNING: Challenge solver lib script version 0.3.2 is not supported ... supported version: 0.8.0
|
||
```
|
||
|
||
…then the EJS solver helper is stale. `yt-dlp -U` does **not** update it. Upgrade explicitly:
|
||
|
||
```bash
|
||
pip3 install --user -U yt-dlp-ejs
|
||
```
|
||
|
||
### SABR-Only Streaming Warning
|
||
|
||
Some videos may show:
|
||
|
||
```
|
||
WARNING: [youtube] Some android_vr client https formats have been skipped as they
|
||
are missing a URL. YouTube may have enabled the SABR-only streaming experiment.
|
||
```
|
||
|
||
This is a YouTube-side experiment. yt-dlp falls back to other clients automatically — no action needed.
|
||
|
||
### pip Version Check
|
||
|
||
`pip show` does not accept `--break-system-packages`. Run separately:
|
||
|
||
```bash
|
||
yt-dlp --version
|
||
pip show yt-dlp
|
||
```
|
||
|
||
### Format Not Available: Strict AVC+M4A Selector
|
||
|
||
The format selector `bestvideo[vcodec^=avc]+bestaudio[ext=m4a]` will hard-fail if YouTube doesn't serve H.264 (AVC) video for a given video:
|
||
|
||
```
|
||
ERROR: [youtube] Requested format is not available. Use --list-formats for a list of available formats
|
||
```
|
||
|
||
This is separate from the n-challenge issue — the format simply doesn't exist for that video (common with newer uploads that are VP9/AV1-only).
|
||
|
||
**Fix 1 — Relax the selector to mp4 container without enforcing codec:**
|
||
|
||
```bash
|
||
yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio' \
|
||
--merge-output-format mp4 \
|
||
-o "/plex/plex/%(title)s.%(ext)s" \
|
||
--write-auto-subs --embed-subs \
|
||
https://youtu.be/VIDEO_ID
|
||
```
|
||
|
||
**Fix 2 — Let yt-dlp pick best and re-encode to H.264 via ffmpeg (Plex-safe, slower):**
|
||
|
||
```bash
|
||
yt-dlp -f 'bestvideo+bestaudio' \
|
||
--merge-output-format mp4 \
|
||
--recode-video mp4 \
|
||
-o "/plex/plex/%(title)s.%(ext)s" \
|
||
--write-auto-subs --embed-subs \
|
||
https://youtu.be/VIDEO_ID
|
||
```
|
||
|
||
Use `--recode-video mp4` when Plex direct play is required and the source stream may be VP9/AV1. Requires ffmpeg.
|
||
|
||
**Inspect available formats first:**
|
||
|
||
```bash
|
||
yt-dlp --list-formats https://youtu.be/VIDEO_ID
|
||
```
|