The TRANSFORMERS_IMPORT hint and the ASR pipeline error told users to reinstall torch + torchaudio + transformers. torchvision — the package whose ABI mismatch actually produces this exact lazy-import wording (#1357's torchvision::nms, wrapped into "Could not import module 'AutoFeatureExtractor'") — was the one package the advice omitted. Following it to the letter left the broken package untouched (#1376).
Both surfaces now name the mismatch as a cause and prescribe the pinned reinstall with literal versions (desktop installs ship no deploy/, so the constraint-file form fails there) targeting the venv explicitly. A lockstep test asserts the exact command on every advice surface against deploy/torch-constraints.txt, so a pin bump stays red until the advice matches. Docs gain the same-wording-different-cause section (1a-bis).
Windows users behind a corporate/antivirus TLS-inspecting proxy got a raw
`[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE]` on every model install — the TCP
connection reaches the server fine, but the handshake fails because the
OS trusts the proxy's re-signed root CA and Python's bundled certifi CA
list doesn't. A genuinely different failure mode from #984 (that was
TCP-level unreachability to a blocked host, before any TLS negotiation).
- backend/core/failure.py: new SSL_HANDSHAKE_FAILURE classification
(handshake/cert-verify-failed/sslv3_alert/sslcertverificationerror
substring markers) with an actionable hint, added to
_CONTEXT_FREE_HINT_CLASSES so append_hint() (already called by
setup/download.py's install worker) surfaces it without further wiring.
- backend/main.py: truststore.inject_into_ssl() at module level, before
any huggingface_hub/requests/httpx network I/O — patches ssl.SSLContext
to verify against the OS trust store instead of only certifi's bundled
CA list. Not platform-gated (correctness improvement everywhere);
wrapped in try/except so it never blocks startup.
- pyproject.toml/uv.lock: truststore>=0.9 — pure Python, MIT, PyPA-
maintained, zero transitive deps, same class of fix as socksio.
Verified: uv lock --check + uv sync --frozen clean (lockfile diff is
just the one new package); main.py imports cleanly; full backend suite
passes; no hiddenimports entry needed (main.py is PyInstaller's direct
entry script per backend.spec, so a top-level import traces normally —
unlike socksio's case, which was httpx's internal lazy import).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(net): SOCKS-proxy users can synthesize again — ship socksio, cache-first model resolution, degrade LLM clients (#959)
Under ALL_PROXY/HTTPS_PROXY=socks5:// without socksio installed, httpx
raises ImportError AT CLIENT CONSTRUCTION ("Using SOCKS proxy, but the
'socksio' package is not installed"). huggingface_hub's get_session()
builds exactly that client inside snapshot_download, so POST /generate
500'd with the bare message even for a fully installed model, and
preload_model's model_info probe hit the same error and silently
skipped warm-up. Latent since v0.3.5 — #947's fresh-process engine
spawning unmasked it in v0.3.10 by handing the user's proxy env
directly to a clean backend process.
Three layers, so the class (any session-construction failure) is dead,
not just the reported instance:
* Ship SOCKS support: socksio>=1.0 in [project] dependencies (pure
Python, MIT, zero transitive deps) AND in backend.spec hiddenimports
— httpx imports it lazily in try/except, so PyInstaller's tracer
misses it and the frozen installers would stay broken without the
explicit entry. uv.lock regenerated; `uv lock --check` and
`uv sync --frozen` (the Docker/release bootstrap semantics) verified.
* Cache-first model resolution: from_pretrained's snapshot resolution
extracted into _resolve_snapshot_dir() — local dir, else
snapshot_download(local_files_only=True) (a complete cache resolves
with NO HTTP session constructed), else the original network path.
preload_model's failed network probe now falls back to a cache-only
check and warms up anyway instead of silently skipping (honest log
either way).
* Class guards: resolve_skill_client wraps OpenAI() construction —
env-shaped construction failures degrade to the existing "LLM
unavailable" contract instead of 500ing the calling feature; and
core.failure learns SOCKS_PROXY_SUPPORT_MISSING with an actionable
hint, appended on the raw-string surfaces (global 500 handler,
model-install SSE) via the new append_hint().
Fail-before/pass-after verified by reverting the fix: 11 of the 12 new
tests fail pre-fix (the remaining one is the unchanged network-fallback
contract). 165 tests green across the touched suites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(changelog): add SOCKS-proxy resilience under [Unreleased] (#966)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A per-chunk temp-WAV write that fails with OSError EINVAL ("[Errno 22]
Invalid argument") — a missing/read-only/full temp dir, a removed drive,
or antivirus — collapsed into "Transcription produced no segments.
[Errno 22] Invalid argument" with no next step. classify() now names the
class (OS_INVALID_ARGUMENT) so build_failure attaches an actionable
temp-dir/disk/AV hint at the exact surface the streaming dub path already
feeds it (dub_core.py:672) — same treatment the ffmpeg and compute-type
classes get. Fail-before/pass-after regression added; the errno-22 token
keeps it from colliding with the errno-2 transformers-import class.
Also stamps the [0.3.9] CHANGELOG section with today's release date
(2026-07-04) ahead of tagging.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A model load failed with `[Errno 2] No such file or directory:
'…/site-packages/transformers/models/qwen3/modeling_qwen3.py'` — the user's
transformers install was incomplete (the file is missing while a correct 5.3.0
install has it; an interrupted `uv sync` / antivirus / partial update drops it).
The System Check showed the raw path + "Check logs and try restarting", which is
useless — restarting can't restore a missing file.
Two fixes:
1. core.failure.classify(): recognize this corrupted-install variant. It's a
FileNotFoundError, not an ImportError, so the existing TRANSFORMERS_IMPORT
match ("could not import module"/"AutoFeatureExtractor") missed it. Now also
matches a "no such file"/"errno 2" + "transformers" + "site-packages" signal
(substrings checked separately so it works on POSIX `/` and Windows `\`
paths). An unrelated package's missing file is NOT mislabelled.
2. model_manager._load(): build the /model/status error via build_failure so it
carries the classified hint AND strips the home dir, instead of storing the
raw str(exc). The System Check now shows "Your transformers install is
incomplete. Reinstall it (uv pip install --reinstall transformers) or switch
ASR to faster-whisper" — the existing TRANSFORMERS_IMPORT hint.
Docs: troubleshooting §1a documents the error + the reinstall fix.
Test: test_failure_classify.py pins the POSIX + Windows path forms classify as
TRANSFORMERS_IMPORT with a "reinstall" hint, and that an unrelated package's
missing file does not.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#564 ("No module named 'omnivoice'") is the backend failing to import its OWN
package at the first model call (the dub SSE error on dub:upload). `omnivoice`
is an editable install, so an interrupted/offline `uv sync` that installed deps
but never laid the editable record, an antivirus-quarantined
`_editable_impl_omnivoice.pth`, or an upgrade where only the lock-gated drift
sync ran leaves the venv able to start uvicorn yet unable to import omnivoice —
it boots fine and only fails at runtime, so the bootstrap health gate and the
exit-based broken-venv self-heal (which only see a process that won't start)
never catch it.
Fix the whole class at the import layer: main.py now also appends the project
root (the parent of backend/, where the desktop layout always copies
omnivoice/) to sys.path, guarded on omnivoice/__init__.py existing. The backend
then resolves omnivoice from source regardless of the editable-install state —
covering every variant above. Appended (not inserted) so a real
site-packages/editable install keeps precedence and it can't shadow a different
omnivoice; a no-op in Docker (no sibling omnivoice/) and a harmless duplicate
in a dev checkout.
Also routes "No module named 'omnivoice'" through failure.classify() →
BROKEN_VENV so, if it ever still surfaces, the toast points at Clean & Retry
instead of a bare import error. Regression test covers the classify mapping and
its negative guard (a legitimately-named omnivoice_* helper must not match).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two robustness fixes that share the failure taxonomy:
- Video downloads (#554 douyin "Unsupported URL", #536 "Broken pipe"): yt-dlp's
raw error surfaced with no next step. classify() now names UNSUPPORTED_VIDEO_URL
(non-downloadable link shape — paste a direct video page or drop a file) and
VIDEO_DOWNLOAD_NETWORK (transient CDN/network drop — just retry; the partial
download is already cleaned up), each with an actionable hint. yt-dlp is on a
current pin, so this is graceful classification, not a dependency bump.
- "No module named 'encodings'" (relocated/copied/restored venv whose interpreter
can't bootstrap its stdlib — exit 1, not 106): slipped BOTH #314 self-heal
matchers, so the user saw the error forever. Widen
backend_exit_indicates_broken_venv to also match the full quoted phrase, routing
it into the existing rebuild-once self-heal. Kept narrow so an app-level import
of an 'encodings'-prefixed package can't trigger a rebuild. Plus a BROKEN_VENV
hint for the case where the rebuild itself can't run.
Tests: classify() maps the 3 new classes with hints (a generic reason still ""),
and the 'encodings'-prefixed-package negative guard holds; the Rust matcher test
gains the encodings positive + negative cases. 5 pytest passed; the matcher is
compiled by CI's Tauri shell check.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#551: both CTranslate2 ASR backends request compute_type="float16" on CUDA with
NO fallback. On GPUs without efficient fp16 (older Maxwell/Pascal, GTX 16xx) or a
CTranslate2/cuDNN binary mismatch, WhisperModel/whisperx.load_model raise a
ValueError at construction — which escaped the existing OOM-only `except
RuntimeError`, so every chunk failed and the user got "Transcription produced no
segments". Add a per-device compute_type fallback chain (cuda: float16 →
int8_float16 → int8; cpu: int8 → float32) to both backends + the ASR sidecar,
alongside (not replacing) the existing OOM→CPU path, with an ASR_COMPUTE_TYPE
override for exotic hardware (documented in README).
Also in the same ASR-robustness pass:
- #549: PyTorchWhisperBackend._ensure_pipe wraps the transformers pipeline load
and re-raises an actionable error (reinstall transformers / use faster-whisper)
instead of a bare "Could not import module 'AutoFeatureExtractor'".
- #516: the /dub/transcribe SSE generator is wrapped so it can NEVER close
without a terminal event — any unanticipated exception now yields a structured
`error` (with build_failure's hint) + `done`, turning "stream dropped, likely
ASR failed" into the real cause + Retry.
- failure.py: COMPUTE_TYPE_UNSUPPORTED + TRANSFORMERS_IMPORT classes so the
no-segments toast is actionable.
Tests (fail-before/pass-after): float16-unsupported → int8 for both WhisperX +
FasterWhisper; a generic non-OOM RuntimeError still raises; classify() maps the
two new classes; the SSE stream always terminates with error→done. 7 + 1 passed,
17 in the failure suite (no regression).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>