Skip to content

v2.18.1 — the backends config promised, and the privacy holes they opened

Released 2026-08-13. Upgrade:

sudo snap refresh yazses     # snap
pipx upgrade yazses          # or: uv tool upgrade yazses

Two backends that config offered but no build could run (#70, #71)

[voiceprint] backend = "resemblyzer" and [recimport]/[meeting] backend = "pyannote" were accepted by the config loader and then reported as not implemented in this build. Both now ship an adapter, each behind its own extra — voiceprint-resemblyzer and diarization-pyannote. Neither may advise the extra it sits beside: voiceprint is speechbrain and diarization is sherpa-onnx, so naming those would send you after a package that cannot supply the backend you picked. sherpa and ECAPA remain the defaults.

Resemblyzer does not simply call embed_utterance. That function slices audio into 1.6 s partials and zero-pads anything shorter, with no flag to disable it — so a 500 ms Cocktail Filter window would be encoded as 0.5 s of speech plus 1.1 s of digital silence. Below the partial length the adapter runs the encoder directly on the frame instead. Measured same-speaker similarity against a full-utterance reference: 0.647 vs 0.509 at 500 ms and 0.766 vs 0.605 at 1000 ms, converging by 1500 ms. The Cocktail Filter's default target_threshold is 0.5, so the padding was dragging a speaker's own voice onto the rejection line.

Enabling pyannote diarization would have sent usage data off the machine

pyannote.audio 4.x ships OpenTelemetry tracking that is on by default: its bundled telemetry/config.yaml sets metrics_enabled: true with an OTLP endpoint at otel.pyannote.ai, and its track_pipeline_apply hook reports the duration of the audio being diarized along with the requested speaker counts. telemetry_log_level is pinned to CRITICAL, so it says nothing either way. This is not an optional extra — opentelemetry-exporter-otlp and pyannoteai-sdk are hard requirements of the package.

No audio would have been transmitted, but the length of your meetings and your usage pattern would have been, silently, from a tool whose entire promise is that nothing leaves the machine (ADR-011). The backend now sets PYANNOTE_METRICS_ENABLED=false before importing pyannote — the ordering is the mechanism, because upstream defaults the variable from its config file only when it is not already set, so disabling it afterwards would let the import-time tracker fire first. A test pins the ordering, not just the value.

Dictation cleanup could POST your transcribed text to any host

The optional LLM cleanup path took an endpoint from config and sent the transcript to it with no restriction on where "it" was. A typo, a copied config, or a well-meaning suggestion could route every dictated sentence to a third party. The endpoint is now guarded, and the privacy statement — which had claimed cleanup made "no HTTP API call" — says what the code actually does.

Windows: hold-to-talk, command keys, and the liveness probe that killed the daemon

Eleven defects in platform/windows/, found by audit and each pinned by a regression test. Three of them meant core features could never have worked on Windows:

  • yazses status terminated the daemon. is_running() used os.kill(pid, 0) as a liveness probe. That is the POSIX idiom; on Windows CPython's os.kill has no signal semantics and falls through to TerminateProcess(handle, sig), so signal 0 kills the process with exit code 0 (bpo-14480). status, doctor and the tray's poll loop all reach it. Replaced with OpenProcess + GetExitCodeProcess.
  • Hold-to-talk never started. The hold threshold was only re-checked when another key event arrived, which assumes typematic auto-repeat. Modifier keys — every supported hotkey except space — do not repeat, so with the default right_ctrl the single keydown landed at t=0 and the next event was the keyup.
  • Every named command key was a no-op. The VK table was keyed capitalised ("Return") but looked up lower-cased, so Return, Tab, Escape, BackSpace and the arrows all resolved to vk=0 — a keystroke Windows accepts and discards.

Also fixed: 64-bit handle truncation, a named-pipe handle leak that left IPC dead after eight failed accepts, an IPC client that stored a timeout and never applied it, and an autostart value written unquoted that broke on any username containing a space.

Also

  • brew install --cask yazses works, via a real tap
  • Every macOS .app since v0.1.3 told the OS it was version 0.1.2
  • DeepFilterNet noise suppression stays honestly unimplemented (#69) — every release caps numpy<2.0, so there is deliberately no denoise extra rather than a config key nothing can honour
  • Windows packaging manifests no longer ship a release behind — scoop, chocolatey and winget sat at 2.17.0 carrying the previous release's checksum

The full list for this release is in CHANGELOG.md.