issue #122 'Extract: Unknown Error' on Windows: 'bun run dev' fails, running
backend+frontend separately works. Root cause: dev:api launches uvicorn with
--reload, so use_subprocess=True, and uvicorn 0.42's asyncio_loop_factory
EXPLICITLY forces the SelectorEventLoop on Windows in that case (passed as
loop_factory to asyncio_run, overriding any policy). The SelectorEventLoop has
no subprocess support -> asyncio.create_subprocess_exec raises
NotImplementedError. 'python backend/main.py' (no reload) uses ProactorEventLoop
-> works. So an event-loop-policy fix is futile; the thread fallback is the fix.
The ffmpeg extract path already routed through _spawn_async's thread fallback
(landed in #157), but several other spawn sites used raw create_subprocess_exec
and stayed broken on the dev loop:
- add public spawn_subprocess() (drop-in for create_subprocess_exec) that routes
through _spawn_with_retry -> _spawn_async (NotImplementedError -> thread
fallback + EAGAIN retry); native asyncio path unchanged on supported loops.
- fix _spawn_thread_fallback to forward cwd/env/etc. to subprocess.Popen (was
silently dropping them -- breaks sonitranslate's cwd= pip install).
- convert raw spawns: dub_generate atempo, tools ffprobe, gallery yt-dlp (x2),
sonitranslate install (x4). translation_engines already had its own fallback.
- tests: NotImplementedError -> thread fallback; cwd forwarding; stdin input
(atempo); native path unchanged.
No behavior change off the broken loop (macOS/Linux/Windows-prod): the native
asyncio subprocess is still used; the fallback only triggers on NotImplementedError.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>