v2.38.0 — the failures that never told anyone¶
Released 2026-09-24. Upgrade:
sudo snap refresh yazses # snap
pipx upgrade yazses # or: uv tool upgrade yazses
scoop update yazses # Windows
One theme, found by auditing every path that hits an error or needs a permission against a single rule: say so, with a reason the user can act on, somewhere they will actually see it. Four defects fell out. Two were shipping.
Every permission denial was reported as a microphone problem¶
mic-permission's marker was the bare word "permission", and the classifier folds an exception's class name into the text it matches. Every PermissionError therefore matched that rule first — and a /dev/uinput denial, a /dev/input/event* denial and a refused portal consent are all PermissionErrors.
Reproduced against the real classifier before anything was changed:
| what actually failed | what the user was told |
|---|---|
/dev/uinput denied (typing) | "YazSes is not allowed to use the microphone" |
/dev/input/event3 denied (hotkey) | "…use the microphone" |
| portal consent refused | "…use the microphone" |
So someone who declined the Wayland typing prompt was sent to the audio privacy pane to fix a typing fault — advice that cannot work. inject-permission sat below it and was unreachable dead code for the errors it was written for.
Seven permission families now name themselves, each with the command that actually fixes it: refused portal consent, /dev/uinput, /dev/input/event*, the snap's audio-record and raw-input interfaces, and macOS Accessibility and Input Monitoring. The microphone rule now has to carry audio evidence of its own, and an unrelated PermissionError falls to the generic diagnosis — which is what earns the Prepare a bug report button, the right answer when YazSes genuinely does not know.
Spoken Edit and Punch-In erased your text and then lost it in silence¶
Both erase the previous dictation and retype it. Spoken Edit wrapped the pair in a try whose handler was a log.debug, so when the retype failed the erase had already landed: the sentence disappeared, nothing replaced it, and the only record was a debug line nobody reads. Punch-In had no try at all — a CLI caller saw the exception, but a correction triggered by voice or from the tray lost the text with nobody to tell.
Both now report through the path that reaches the person holding the key. There is deliberately no automatic restore: a failed injection may have typed part of the replacement, and retyping the original on top of a partial write corrupts it further. Spoken Edit also no longer records the replacement in its ledger — writing text there that never reached the screen would make "scratch that" delete characters that are not on it.
No permission was checked at startup at all¶
Every probe and every remedy string in platform/*/permissions.py already existed. Their only consumer was yazses doctor, a command you have to know to run. So the daemon would start with keyboard access denied, reach IDLE, paint the tray healthy, and let you hold the key into a void — which is, word for word, what the first person to run the macOS build reported on #182: hotkey pressed, "nothing happens", no prompt, no explanation.
Missing grants are now announced on the desktop at startup, carrying the platform's own remedy text. Only DENIED speaks. UNKNOWN stays silent, because macOS reports a microphone nobody has been asked about yet as NotDetermined, and an absent PyObjC reports UNKNOWN too — reddening a working install on the strength of a probe that never ran is a mistake this project has already made once. A probe that raises is treated the same way.
The Wayland consent dialog now explains itself before it opens¶
RemoteDesktop is the only Wayland API for synthetic input, so the portal backend added in v2.37.0 has to use it — but GNOME and KDE title the dialog "Remote Desktop" and confirm it with a button marked "Share". Read cold by someone who installed YazSes because nothing leaves their machine, that says the opposite of what is happening, and Cancel is the rational answer.
YazSes now speaks first: what is actually requested is the keyboard alone — no screen capture, no mouse, nothing sent anywhere, approve once and it is remembered. Every clause is checked against what the client requests rather than asserted. yazses start also names the consequence of skipping yazses setup, so the terminal pre-empts the surprise instead of racing it. If a restore token already exists no dialog is coming, and nothing is said.
Also fixed¶
- A first run that downloads a model says so. The speech model is several hundred megabytes and was fetched with a
log.infoand nothing else, so a GUI install looked like an app that started and then did nothing for minutes. A blocked model download was #310, the first bug reported by a real user. Silent on every later run, and in offline mode. - A crash loop no longer turns one problem into five toasts. The repeat-silence record lived in memory, so it could only damp repeats inside one process — while
Restart=on-failurewithStartLimitBurst=5produces five fresh processes a minute. It now survives restarts, written atomically because the daemon is killed at arbitrary moments by that very loop. - Failures printed to a terminal nobody was watching now reach the desktop. This is the mechanism behind an enabled engine silently disappearing on the next
uv tool upgrade: the install succeeds, the caller is told it worked, and the loss surfaces much later as dictation quietly using a different engine. - The bug-report offer is a decision, not a naming accident. It was derived from a slug prefix, which meant a future recognised-but-unfixable fault would inherit "no button" because nobody was asked the question.
Verification¶
Both shipped defects were reproduced against the real code before being fixed, and each fix was mutation-tested — the change was reverted to confirm its test goes red rather than passing vacuously. Gates at release: ruff clean, mypy clean across 525 source files, 15,407 tests passed / 300 skipped.
The macOS permission announcements are covered by unit tests against fake backends and by the macOS CI leg; they have not been confirmed by a human on a physical Mac. A field report on #182 remains the most useful thing anyone with a Mac can contribute.