Give the app an install command that no OS gate stands in front of - #32
Merged
Conversation
The release zips are unsigned PyInstaller bundles, so Gatekeeper and SmartScreen both stop them and the README has to teach a four-step dance around System Settings. Signing is a separate paid track, and Tauri would not have removed the need for it — under the sidecar design the PyInstaller binary ends up inside the bundle and needs notarizing either way. A wheel sidesteps the whole category: pip and uv install into an environment the user already trusts, so nothing is a downloaded application and no gate applies. `uv tool install amicoscript` is now the recommended route, identical on all three platforms, and it fits because the heavy dependencies were already deferred — transcription goes through CTranslate2 and never imports torch. The repo is not laid out as a Python package: backend/ is a directory of flat modules that run.py puts on sys.path. Rather than restructure it, hatchling force-include mappings reproduce the repo's shape inside the package, so run.py's BASE_DIR and main.py's FRONTEND_DIR resolve correctly with no packaging-specific branches. cli.py is the only new runtime code, and run.py grows a main() that the console script, `python run.py` and PyInstaller share. That approach has one bad failure mode: a broken mapping yields a wheel that builds, installs and starts, serving nothing. scripts/check_wheel.py asserts the payload is present — including every vendored asset index.html references — and the workflow additionally installs the wheel and checks it serves. Both run on dry runs, so a tag is never the first execution of this path. Publishing uses PyPI trusted publishing, so there is no token to store. It is gated behind the GitHub release because a PyPI version is spent on first upload and cannot be reused, and the tag is checked against VERSION for the same reason. release now also needs wheel, so a failed wheel cannot leave a version published on GitHub but absent from PyPI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JidoPdgd5BDroVFcEdF52
pyannote.audio 4.0 landed after v1.16.0 and requires torch>=2.8. The cu121 runtime pins torch<2.7 because the cu121 index has nothing newer and never will, so with the range left open pip resolves pyannote to 4.x and then fails: ResolutionImpossible. generate_runtime_manifest.py runs before PyInstaller, so this took out the Linux and Windows builds entirely — macOS survived only because it resolves no CUDA variant. The CPU runtime is the less obvious half. It has no torch ceiling, so it kept resolving green while silently moving to pyannote 4 — a major the backend was not written against, and a different major from the one GPU machines would get. A green build was hiding that, which is why the cap goes in both files rather than only in the one that failed. Capping at <4 restores what v1.16.0 shipped and resolves to pyannote 3.4.0 with torch 2.6.0. The wheel's diarization extra gets the same cap, so a pip install does not land somewhere else again. Moving to pyannote 4 is a real upgrade — torch 2.8+, a CUDA index newer than cu121, and backend/core/diarization.py updated for the 4.x API. Deliberate work, not a range left open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JidoPdgd5BDroVFcEdF52
Capping pyannote at <4 in the previous commit fixed the cu121 resolve and broke the CPU one. With torch left open, the resolver pairs pyannote 3.4 with the newest torch on the CPU index and finds no consistent solution for the tree pyannote 3 pulls in — lightning, speechbrain, torchmetrics all move with torch. Bounded to <2.7 it lands on torch 2.6.0 and torchaudio 2.6.0, a matched pair, the same generation the cu121 flavour resolves to. Unbounded it also drifted to torch 2.13 with torchaudio 2.11 — a mismatched pair pip is willing to install. Two runtimes that agree on the pyannote major but not the torch generation were never the parity this was after. The two ceilings move together now. Raising one alone puts the flavours back out of step, which is the failure this pair of commits exists to close. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JidoPdgd5BDroVFcEdF52
Capping the CPU runtime alongside the CUDA one broke its resolve, twice, in a way that does not reproduce outside CI: the same requirement set against the same bundled pins resolves cleanly to pyannote 3.4.0 with torch 2.6.0 on PyPI, with and without a torch ceiling. The difference is the CPU index itself, which is not reachable from where this was tested, so the cap goes back off the file CI has actually observed green and stays on the one whose failure it explains. That leaves the two runtimes on different pyannote majors — CPU on 4.x, CUDA on 3.x — which is the state main is already in rather than a regression this adds. The cu121 file carries the gap as a comment so the next person does not rediscover it by capping the CPU file again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JidoPdgd5BDroVFcEdF52
Three dry runs went into capping pyannote so a tag could publish, and none of them worked. Capping the cu121 file does not fix its resolve; capping the CPU file as well breaks one that was green. Neither reproduces outside CI — with the same pip generation and the exact bundled pins the build resolves against, the capped set resolves cleanly on PyPI. The variable left untested is the PyTorch index as primary index, which needs a machine that can reach it. So both files go back to exactly what main has, and this branch carries only the wheel and PyPI work, which has passed every run. The wheel's diarization extra drops its cap too, so it mirrors the CPU runtime rather than encoding a decision that is still open. docs/pypi-release.md records the blocker, everything ruled out, and where the fix probably is — forward to pyannote 4 on a newer CUDA index, not backward. A tag still cannot publish until that is done; release needs build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JidoPdgd5BDroVFcEdF52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release zips are unsigned PyInstaller bundles, so Gatekeeper and SmartScreen both stop them and the README has to teach a four-step dance around System Settings. Signing is a separate paid track, and Tauri would not have removed the need for it — under the sidecar design the PyInstaller binary ends up inside the bundle and needs notarizing either way.
A wheel sidesteps the whole category: pip and uv install into an environment the user already trusts, so nothing is a downloaded application and no gate applies.
uv tool install amicoscriptbecomes the recommended route, identical on all three platforms, and it fits because the heavy dependencies were already deferred — transcription goes through CTranslate2 and never imports torch.How the wheel is built
The repo is not laid out as a Python package:
backend/is a directory of flat modules thatrun.pyputs onsys.path. Rather than restructure it, hatchlingforce-includemappings reproduce the repo's shape inside the package, sorun.py'sBASE_DIRandmain.py'sFRONTEND_DIRresolve correctly with no packaging-specific branches.amicoscript/cli.pyis the only new runtime code, andrun.pygrows amain()shared by the console script,python run.py, and PyInstaller.That approach has one bad failure mode: a broken mapping yields a wheel that builds, installs and starts — serving nothing.
scripts/check_wheel.pyasserts the payload is present, including every vendored assetindex.htmlreferences, and the workflow additionally installs the wheel and checks it serves. Both run on dry runs, so a tag is never the first execution of this path.Publishing uses PyPI trusted publishing, so there is no token to store. It is gated behind the GitHub release because a PyPI version is spent on first upload and cannot be reused, and the tag is checked against
VERSIONfor the same reason.releasenow also needswheel, so a failed wheel cannot leave a version published on GitHub but absent from PyPI.Verified
The
wheeljob passed on every dry run of this branch — build,twine check,check_wheel.py, and installing the wheel into a clean venv and confirming it serves the frontend. Locally: 874 tests pass (3 pre-existingtest_tui_deferred_importsfailures, unrelated), and a wheel rebuilt from the sdist is also complete.What this PR deliberately does not change
backend/requirements-diarization.txtandrequirements-diarization-cu121.txtare byte-identical tomain.A separate, pre-existing problem blocks tagging:
pyannote.audio4.0 requirestorch>=2.8, the cu121 index has nothing newer than the 2.6 line, andgenerate_runtime_manifest.pyfails withResolutionImpossibleon Linux and Windows.maincannot cut a release today either — this PR neither causes nor fixes that.Several attempts to fix it here were reverted because they did not work, and one made things worse.
docs/pypi-release.mdrecords the blocker, everything ruled out, and where the fix probably is. Merging this lands the wheel plumbing; a tag still will not publish until the diarization resolve is fixed, becausereleaseneedsbuild.