Repository navigation
YouTube imports fail: JS challenge solver (EJS remote component) is never enabled #432
Description
What happens
Every YouTube import fails on a fresh Windows portable install. The UI shows
Audio processing failed. Please try another video. and the job's
error_detail is unknown, so the cause is only visible in data/logs/.
There are actually two independent blockers stacked on top of each other.
This issue is about the second one; I'll open a PR for the first separately.
1. Bot check (cookies)
HTTP Error 429: Too Many Requests
ERROR: [youtube] <id>: Sign in to confirm you're not a bot.
yt-dlp's documented remedy is to send the user's cookies, but there is no way
to configure that today — pipeline/download.py never sets cookiefile or
cookiesfrombrowser. I have a change for this ready and will open it as a
draft PR.
2. JS challenge solver — the part I'd like guidance on
With cookies configured, the bot check clears and the failure moves further
down the pipeline:
WARNING: [youtube] [jsc] Remote components challenge solver script (deno) and
NPM package (deno) were skipped. These may be required to solve JS challenges.
You can enable these downloads with --remote-components ejs:github
WARNING: [youtube] <id>: n challenge solving failed: Some formats may be missing.
WARNING: Only images are available for download.
ERROR: [youtube] <id>: Requested format is not available.
The metadata probe succeeds, then every media format is filtered out, so
format: "bestaudio/best" matches nothing.
Recent yt-dlp moved YouTube's signature/n-challenge solver ("EJS") out of the
package into a component fetched at runtime, disabled by default. StemDeck
never enables it, so on a current yt-dlp the download cannot resolve a format.
Confirmed with the bundled interpreter, same URL, only the flag differing:
# fails
python -m yt_dlp --cookies-from-browser firefox -f bestaudio --simulate <url>
-> ERROR: Requested format is not available
# works
python -m yt_dlp --cookies-from-browser firefox -f bestaudio --simulate \
--remote-components ejs:github <url>
-> format 251 webm 137k
Not a PATH problem: it fails identically with a JS runtime (deno 2.9.1) on
PATH. The missing piece is the solver script, not the runtime.
Worth noting the version direction is counter-intuitive: an older
standalone yt-dlp (2026.06.09) resolves the format fine, while the newer
bundled one (2026.08.19) does not — because the newer one expects the
component that is off by default.
Why I'm opening an issue instead of a PR
Enabling remote_components makes yt-dlp download and execute a solver script
from the yt-dlp GitHub org at runtime. That is a trust/threat-model decision
for the project, not something a drive-by PR should decide, and it interacts
with the CSP/SSRF care already visible in download.py. Options as I see them:
- An opt-in setting, defaulting off (mirrors yt-dlp's own default). This is
what I prototyped locally to confirm the diagnosis. - Enabled by default, on the grounds that YouTube import is a headline feature
and is otherwise broken out of the box. - Ship the EJS component inside the portable build so nothing is fetched at
runtime. - Pin the bundled yt-dlp to a version that does not need it (stopgap only).
Happy to send a PR for whichever direction you prefer.
Unrelated but adjacent
error_detail is unknown for these failures, so diagnosing anything here
means reading data/logs/. Propagating yt-dlp's message would have made both
of the above self-evident. Can split that into its own issue if useful.
Environment
- StemDeck v0.13.0, Windows x64 portable (CPU-only)
- Windows 11, bundled yt-dlp 2026.08.19
- Reproduced on multiple videos
Activity
thcp commentedon Aug 25, 2026
Thanks for this. The diagnosis is right, and opening it as an issue rather than a PR was the right call. Enabling a runtime code fetch is not something a drive-by change should decide.
There is a fifth option none of us listed, and it removes the trust question instead of answering it.
yt-dlp-ejsis on PyPI. It is a 53 KB pure-Python wheel with no dependencies, and yt-dlp checks for it as an installed package before it considers any remote source. So the solver ships with the app, nothing is fetched at runtime, and--remote-componentsstays off.I reproduced your failure exactly. Forcing a client that needs the challenge, with a runtime present and no solver:
Installing the package clears it. The solver runs and the format resolves. I also measured quickjs-ng against node, since a 2 MB engine against deno's 110 MB matters a lot for a portable build. quickjs solves it in about 10 seconds versus node's 4. That sits in front of a separation that takes minutes, so it is affordable, but a second unsigned binary in the bundle and three build scripts to fetch it is a packaging decision I want to look at properly before committing to it.
One thing your report helped me find that I would not have looked for otherwise. The two blockers are not independent. Supplying cookies makes yt-dlp drop every client that does not support them, and those are exactly the clients resolving formats today without any solver. So cookies alone would have made imports worse for people who currently work. That is why the cookie setting I just landed is a fallback rather than a default: the first attempt never carries cookies, and they are only used after YouTube has actually turned us away. By then the path they would have displaced has already failed.
Landed now:
error_detailcarries the real yt-dlp message instead ofunknown. You were right that this was the thing making everything else hard to see, and it was two separate gaps, not oneThe solver itself is not settled yet. I am holding it for further investigation and tracking it separately in #438, which records everything above so none of it has to be worked out again. Extraction still works today through the fallback clients, so this is not urgent, but yt-dlp has deprecated that path and it will not last forever.
One caveat worth setting now. Even with cookies and the solver, a flagged IP may still be short a PO token, which yt-dlp cannot generate on its own. My test of the
webclient failed for that reason with the solver working fine. So this may improve your case without fully fixing it, and I would rather say that up front than have you find out after the release.If you still have the setup that reproduced this, I would be glad to know whether the cookies option alone moves the needle for you, and later how it behaves once the solver lands. Let me know how it goes.
YouTube import diagnostics, an opt-in cookies file, and 0.75x playback (
Ship YouTube's challenge solver without costing anyone a reinstall (#446
diegoarusso commentedon Aug 25, 2026
Thanks — the
yt-dlp-ejspointer was the missing piece, and it does exactlywhat you said. Confirmed on the Windows x64 portable build:
pip install yt-dlp-ejsinto the bundled interpreter,remote_componentsleft off, and aYouTube import now runs end to end. Nothing fetched at runtime.
Did cookies alone move the needle?
No — and that lines up with your fallback design.
With cookies configured and no solver, the bot check cleared but the import
still failed, just further down:
So on this setup cookies alone were necessary but not sufficient. The import
only worked once the solver was present. Worth knowing if you were expecting
the cookies release alone to unblock people.
Your point about cookies displacing the clients that resolve formats without a
solver also explains something I had written off as noise: the older
standalone yt-dlp on this machine (2026.06.09) resolves formats fine with no
solver at all, while the bundled 2026.08.19 does not. I had put that down to a
version difference. Making cookies a fallback rather than a default is clearly
the right call, and my patch applied them unconditionally on every request, so
it would have hit exactly the regression you describe. Not pursuing it.
One data point for #438: node is not detected, deno is
Since you were weighing quickjs-ng against node, this may be worth checking
before you commit to a runtime. With
yt-dlp-ejs0.8.0 installed and only thePATH differing:
Note
JS runtimes: nonein the first case: node is present and executable butnever registers as a runtime at all, so this looks like detection rather than a
solving failure. I have not dug into why. One guess is that the PyPI
distribution carries the bundle deno can consume and that node wants the npm
one — the original warning did name the script and the NPM package separately —
but that is speculation on my part and I have not verified it.
Environment: Windows 11, StemDeck v0.13.0 portable, bundled yt-dlp 2026.08.19,
Python 3.12.7,
yt-dlp-ejs0.8.0. Single video, so treat it as one data pointrather than a general claim. Happy to run whatever else is useful — I still
have the setup.
PO token
No sign of it here so far. With cookies plus the solver, format 251 resolves
and the download completes, so whatever client that path lands on is not
short a token on this IP. If that changes I will report back.
On the diagnostics
The failure classification is going to save the next person a lot of time.
error_detail: unknownmeant every one of these had to be diagnosed by readingdata/logs/— I only found the second blocker because the first one's messagedisappeared and something different showed up underneath it.
test: cover the video-track fetch and the portable-build download opt…