Skip to content

v2.32.0 — the release that stopped trusting a clean return

Released 2026-08-26. Upgrade:

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

33 commits. Most of the defects fixed here share one shape: the operation finished without raising, reported success, and destroyed or discarded the thing it existed to produce. A clean return was treated as evidence, and it was not.

A meeting no longer disappears into a collapsed transcript

A real 41-minute meeting finalized as status: "done", capture: "ok", with a transcript.md containing 93 repetitions of "Hello, hello, hello." The decoder had collapsed into a repetition loop, and every existing guard passed it: one asks whether audio was heard — it was — and the other asks who said what, and there was one speaker. Neither asks whether the words are real. Because nothing raised, the recording was deleted as a successful consumption, and the only surviving record was a newline-delimited JSON file that nothing rendered and nothing mentioned.

Every meeting is now transcribed twice and both are kept. The rolling live decode is written to live-transcript.md on the way out — before the batch pass runs, so a finalize that dies still leaves it. A new quality check scores the batch transcript on repetition share, distinct-phrase ratio, longest back-to-back repeat, words per minute, and how far the batch decode disagrees with the live decode of the same audio. That last signal is the strongest and needs no threshold.

Thresholds were fixed against five real stored meetings before being chosen: the check catches the collapsed one and fires on none of the four healthy ones. The metrics are written to quality.json for every meeting, healthy or not, because a verdict is only interpretable next to the numbers it did not fire on.

When the verdict is bad the recording is kept regardless of [meeting] retain_audio — a deliberate, one-directional narrowing of the privacy default, because the recording is the only input that can produce a better transcript. The minutes pass is skipped, since a summary of invented words reads exactly like a real one. And yazses meeting recover now accepts a meeting that finished badly, not only one that never finished; it archives previous outputs to attempts/<n>/ rather than overwriting them, because a retry runs precisely when the last result was distrusted and it may come back worse.

Follow a meeting while it is still running

live-transcript.md is now appended to as each utterance is decoded rather than being rendered once at stop. The record already existed throughout the call, but as JSON Lines nobody opens mid-meeting. yazses meeting start prints the path and the tail -f command; yazses meeting status names the same file. The incremental writer and the whole-file re-render produce byte-identical output, so the finalize pass never rewrites the file you have been reading. Disable with [meeting] live_markdown = false.

A new yazses meeting summary says what the meeting produced, where each file is, what each file is for, and — first, above the file list — anything that should stop you reading the transcript as a record. The same readout is written to the folder and sent as a desktop notification, because Meeting Mode has no key held and no terminal watched, and its post-pass ends long after the user has walked away.

The snap crashed on the first command it told users to run

yazses setup executed sudo to install host packages and add the user to the input group. Inside a strictly confined snap none of that is permitted, and the command died with PermissionError: [Errno 13] Permission denied: 'sudo'. It now detects confinement and prints the two snap connect commands that actually apply.

The condition was reproduced from the published 2.31.0 snap in a clean LXD container, which is the only way to meet it on purpose: a developer machine runs from a checkout and never enters confinement. That is why the crash reached a user before it reached a test.

Every surface that described the snap has been corrected to match. The Store description and install docs no longer present yazses setup as a host-provisioning step; snap dictation is labelled X11-only wherever it is offered, with Wayland pointed at the universal installer; and the install checklist now carries snap connect yazses:raw-input, which it had never mentioned — so the one screen telling a user what to do omitted the only step that grants the hotkey. yazses quickstart also stops reporting "Prerequisites — already set up ✓" inside a snap whose interfaces are unconnected: a confined plan is empty because nothing in it is the app's to do, which is the opposite of a provisioned machine.

Related: yazses setup no longer exits with a traceback when a command cannot be executed at all. check=False suppresses a non-zero exit status, not a failure to exec, so a sandbox denial or a missing binary escaped out of the one command whose job is repairing a machine that does not work yet.

Publishing found the revision and threw it away

Both architectures uploaded successfully in the v2.31.0 publish run, and neither reached snapcraft release. Under the restored set -e, a failing command substitution in an assignment aborts the step — so one transient non-zero exit from snapcraft revisions killed the job on the first pass of the very loop written to retry it. The workflow now captures the table through an if, making a failed query a skipped iteration rather than a dead job.

Separately, nothing ever asked a second time whether a release had shipped. The existing check runs at the tag push, which is the one moment a slow channel legitimately answers "not yet" — so a stall was indistinguishable from a release in flight. A scheduled watcher now re-asks after a grace period and compares against the previous release rather than a hand-written list of channels, so it reports a channel that went backwards instead of filing the same six permanently-absent channels every day.

Measured: the decode defaults were put to a 2×2, and both are kept

large-v3 decoding the same 200 test-other utterances five times produced five different WERs (4.84–6.21%). The obvious repair was greedy decoding, and it was about to be recommended. Measuring the full 2×2 — temperature fallback crossed with condition_on_previous_text, five decodes per arm, on large-v3 and on the base.en a default install actually runs — says otherwise, and no default changes. Greedy decoding is the worst arm measured anywhere: 15.26% on large-v3, almost all of it insertions.

Turning conditioning off is what actually fixes large-v3 (3.82%, one hash across five decodes), but it reverses on base.en, which gets worse on both splits. The measurement also found a knob users could not reach, so [stt] condition_on_previous_text is now exposed — and the follow-up sweep gives it a rule rather than a preference: the benefit shrinks monotonically with model size and hits exactly zero at medium.en, where all ten decodes across both arms return a single hash. Leave it alone below medium.en, set it false on large-v3 and above.

One documentation claim did not survive contact with the project's own archive: the config reference called larger checkpoints a "marginal gain on clean speech", while the stored numbers say small.en removes 36% of base.en's errors on clean audio and 41% on hard audio. base.en remains the default — it is the best latency trade for hold-to-talk — but for the true reason.

Also in this release

  • [stt] language was a documented key that did nothing for a year, because all three decode paths hardcoded English. It is now threaded through the single seam they share, and a test fails any decode path that builds its own arguments.
  • A disfluency self-correction trigger ate the sentence before it in ordinary prose.
  • The diarization plausibility guard was near-inert on real meetings — it had been measured on a corpus of a different shape.
  • Sitemap entries now carry each page's own last-commit date instead of claiming all 475 pages changed on every build, and four design-index URLs that appeared twice were de-duplicated.

The full list — every change, measurement and limitation — is in CHANGELOG.md.