v2.33.0 — the release found by running what we shipped¶
Released 2026-08-27. Upgrade:
sudo snap refresh yazses # snap
pipx upgrade yazses # or: uv tool upgrade yazses
scoop update yazses # Windows
18 commits, almost all of them Windows correctness. The theme is narrower than last release's and less comfortable: the test suite could not see any of it. Two of these defects were found by installing the published 2.32.0 build on a clean Windows Server host and running it, and one had been failing every CI leg for a week while passing in every local checkout.
The bundled .exe and .app entered a different CLI from the one we ship¶
yazses doctor from the Scoop-installed 2.32.0 binary died with a UnicodeEncodeError on '✗' — hours after that exact crash had been fixed and released.
The console script installed by pip runs yazses.cli:main. The PyInstaller entry point is a separate file, src/yazses/__main__.py, and it called yazses.cli:app — the Click group itself, one layer below. main() is where the Windows console is put into a printable encoding, where the help text is escaped, and where an unsupported platform is reported as a message instead of a traceback. None of those ran in any bundle we have ever shipped, on Windows or macOS, so three separate fixes were released and reached nobody who installed the .exe or the .app.
The entry point now calls main(), and seven AST-level tests pin it there: the bundle entry may not reference cli.app, pyproject.toml must still map the console script to cli.main, and main() must still do all three of the things it does. Two routes into one program is the kind of duplication that stays correct until the day it doesn't.
A machine with no audio device could not import YazSes at all¶
audio/recorder.py imported sounddevice at module scope. sounddevice calls Pa_Initialize() on import, and on a host with no sound card that does not return an empty device list — it raises PortAudioError. So import yazses.core.daemon was impossible on any such machine, which includes most CI runners, most containers, and every server.
The import is now deferred to first use behind a small accessor. The measured effect on a bare Windows Server 2022 host: the suite went from 5666 tests collected with 46 collection errors to 13449 passed, 0 failed, 0 errors. Forty-five of those 46 errors were this one import.
The remaining two were a related lesson. pytest.importorskip catches only ImportError, so a dependency that fails at load — PortAudioError, OSError, a missing DLL — is not skipped but raised, and a single collection error abandons the entire run. Those tests now go through a shared helper that skips on any exception.
yazses report left a Windows account name in clear¶
The diagnostic bundle redacts your account name with a pattern wrapped in \b…\b. \b asserts a transition between a word and a non-word character, so next to a symbol it demands a word character that isn't there. A Windows machine account name ends in $, and could therefore never match: it went into the report in clear, alongside mohsen. and user- and anything else ending in punctuation.
The boundaries are now applied only on the ends that are word characters. Short names still do not match inside longer words. The test that should have caught this was rebuilding the pattern from its own escaped form and passed on the broken code; it now pins a name deterministically and asserts that the old pattern still fails, so the demonstration cannot quietly stop demonstrating.
CI was red on all eight legs for a hook that is entirely correct¶
hooks/sitemap_dates.py runs git log -1 --format=%cs -- docs/index.md. The test job checked out with filter: tree:0. A treeless clone cannot answer a path-limited git log: deciding which commits touched a path needs the commit trees, so git fetches them one at a time from the promisor remote, the hook's timeout expires, and the date comes back empty. Measured against real clones of this repository — tree:0 fails with "could not fetch tree from promisor remote", blob:none answers in milliseconds.
blob:none keeps the trees and skips file contents, which is where nearly all of the size is. A guard now fails the build if the checkout goes treeless again, and pins the hook's query so the workflow comment cannot outlive its reason.
Scoop installs the native build on Windows on ARM¶
The Scoop manifest declared only a 64-bit entry, so Windows-on-ARM users got the x64 build under emulation even though an ARM64 installer is published. The manifest now carries an arm64 architecture — and the generator removes it when a release has no ARM64 asset, because that build leg is continue-on-error and a stale entry would 404 instead of falling back to x64.
Also in this release¶
- Two workflow-shell test files asked the wrong question about the host. They guarded on
shutil.which("bash"). On a GitHub Windows runner that resolves to the WSL launcher, which is not a shell; on macOS bash is real but the step calls GNUtimeout, which is not there. They now guard on the condition the workflow itself states. - The last twelve Windows test failures were the host, not the product — tests that read the machine they ran on and meant something different on each one.
yazses doctor > log.txtcrashed on Windows when output was redirected to a file under a legacy code page.doctorcalled a Windows install healthy while nothing could actually be transcribed, because it never checked that the decoder could load.doctortold Windows and macOS users to runapt.- The release checksum could describe a binary that no longer existed — a wait loop that counted filenames was satisfied instantly on a re-run.
Upgrade notes¶
Nothing in this release changes configuration, on-disk formats, or defaults. If you installed the Windows .exe or the macOS .app from 2.32.0 or earlier, this is the first build in which yazses doctor works at all — that is the upgrade worth taking.