ci!: stop publishing the "portable" binaries, and delete publish-portable.yml - #4380
Merged
Conversation
…able.yml The portable subvariants (qsvp, qsvplite, qsvpdp, qsvpmcp) existed as a SIGILL fallback -- README described them as "compiled without any CPU features enabled". That distinction no longer exists. `-C target-cpu=native` was removed from the x86_64 publish targets in fd74f8f precisely because it caused SIGILL on GitHub runners, so the regular prebuilts have been equally portable ever since. Today `target-cpu` appears in exactly one place -- .cargo/config.toml's [target.aarch64-apple-darwin] -- and there is no [build] section. publish-portable.yml built no macOS target, so for all five targets it did build there was no CPU-feature difference at all. The shipped artifacts confirm it. Reading the central directory of qsv-22.0.1-x86_64-unknown-linux-gnu.zip: entry uncompressed compressed qsvlite 21,718,792 8,099,201 qsvplite 21,718,792 8,099,201 identical qsvdp 105,779,368 33,982,234 qsvpdp 105,779,368 33,982,234 identical qsvmcp 127,310,080 41,593,747 qsvpmcp 127,310,080 41,594,842 same size qsv 193,374,752 65,763,107 PGO qsvp 179,134,752 61,079,681 non-PGO Matching compressed sizes to the byte is near-conclusive for identical content. Only qsvp differed from qsv, and only because qsv is PGO-optimized while qsvp was not -- an optimization difference, not a portability one. The duplication added ~330MB to that archive, ~255MB of it byte-identical copies of binaries already inside it. Deleting the workflow does not regress the current release: qsvp.exe is already absent from 22.0.1's Windows MSVC zip, because portable's Windows legs build qsvmcp from the release tag and so hit the same viz_static stall that kept Windows from publishing at all. Docs updated accordingly: the SIGILL fallback paragraph and the Variants note now explain that the subvariants are no longer published and why. README's target-cpu claim for Apple Silicon / Windows ARM / ppc64le / s390x is left alone -- it was verified accurate, each of those workflows does set it. KNOWN FOLLOW-UP, external repo: dathere/qsv-easy-windows-installer fetches the qsvp binary. It is already broken for 22.0.1 and must be repointed at qsv. A README note flags this for users until that ships. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
My previous commit's follow-up note said dathere/qsv-easy-windows-installer was
"already broken for 22.0.1", and README carried an [!IMPORTANT] banner telling
Windows users to bypass it. Both were wrong, and the banner steered users away
from an installer that works.
The installer calls api.github.com/repos/dathere/qsv/releases/latest, which
EXCLUDES prereleases. 22.0.1 is published with prerelease=true, so that endpoint
resolves to 21.1.0 -- verified -- and 21.1.0's Windows MSVC zip does contain
qsvp.exe (qsv, qsvlite, qsvmcp, qsvp, qsvplite, qsvpmcp, qsvpy311/312/313).
I had checked 22.0.1's zip, which the installer never fetches.
So this is a deadline, not an outage: the break lands when the first STABLE
release ships without qsvp.exe. Banner removed, and the Variants note now says
portable shipped "up to and including 21.1.0" and is being discontinued, rather
than claiming it is already gone -- the current stable release still has it.
Recorded both cross-repo constraints in the release-prep skill, since they are
release-time checks this repo owns:
- the release TITLE must stay identical to the tag; the installer interpolates
.name into the download URL as the tag, so "v22.0.1" would 404 every user
- confirm the installer has shipped its qsvp -> qsv change before promoting a
release out of prerelease
Both corrections came from the qsv-easy-windows-installer session, which
verified the release metadata independently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ange roborev 4144 (LOW, README.md:305), valid on both counts. `qsvpmcp` was missing from the list of portable subvariants, and "up to and including 21.1.0" contradicted this branch's own CHANGELOG entry, which cites qsvp/qsvplite/qsvpdp/qsvpmcp inside qsv-22.0.1-x86_64-unknown-linux-gnu.zip. 22.0.1's Linux archives really do carry them -- a portable publish run got through its three Linux legs before its two Windows legs were cancelled -- while 22.0.1's Windows archives have no qsvp.exe at all. Added qsvpmcp and replaced the release-range claim with "varies by release and platform", plus an explicit migration mapping so anyone using a portable binary knows which regular one replaces it. Did not adopt the reviewer's suggested "distinguish stable from prerelease" framing literally: portable never built any macOS target, so 21.1.0's Darwin archive has no portable binaries either, and any per-platform coverage sentence would have been wrong in a different way. Naming the variance and giving the mapping is both shorter and true. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qsv-easy-windows-installer v1.1.2 fixed both assumptions this repo was working around: it reads `tag_name` instead of the release title, and extracts `qsv.exe` instead of `qsvp.exe` (074b16b). Verified against the live API rather than taking the release note on trust: `releases/latest` -> tag, the constructed download URL returns 200, and `qsv.exe` is present in the archive -- for 21.1.0 (current stable) and for 22.0.1 (what latest resolves to once it is promoted). So the download and extract path is exercised end to end. The HKCU\Environment\Path edit is not, and needs a real Windows machine. The constraints narrow rather than disappear. Users still on v1.1.1 or earlier extract `qsvp.exe` by hardcoded name; they keep working only because `releases/latest` skips prereleases and so still resolves to 21.1.0, which ships `qsvp.exe`. Promoting 22.0.1 to stable breaks them. That is now recorded as a release-notes item, along with the reason to keep titles == tag for those same users even though v1.1.2+ no longer cares. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… item
I described the ≤ v1.1.1 breakage as an unwrap() panic. Wrong in the way that
matters. Verified against that repo's actual code:
src-tauri/src/lib.rs:8 async fn run_path_update(app_handle: tauri::AppHandle)
-> returns (), not Result; no error channel to the UI
src/App.tsx:25-28 invoke("run_path_update").finally(() => { setLoading(false);
alert("Successfully installed qsv...") }) -- no .catch()
The success alert fires unconditionally. Affected users are TOLD the install
succeeded while nothing was written, so they will report "qsv is not on my PATH"
or "qsv didn't update" — never a crash, never an error message.
That installer also has no self-update or version-check path, so it cannot reach
already-installed v1.1.1 users. The qsv release note is therefore not a partial
mitigation, it is the entire remedy. Reworded for the observable symptom and
added a suggested wording that tells users to verify with `qsv --version` rather
than trust the installer's own success message.
Both corrections came from the qsv-easy-windows-installer session and were
verified here against that repo's code before being written down.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n claim roborev 4155, two LOW findings, both valid. README.md:171 — the Easy-installer download badge still linked v1.1.1, the exact version the release-prep guidance added two commits ago tells users to upgrade away from. The public install path was handing people the broken version. Repointed to v1.1.2 after confirming that release is published (not draft, not prerelease), is that repo's `latest`, and that the asset URL returns 200. release-prep/SKILL.md — the verification sentence claimed releases/latest was checked "for both 21.1.0 and 22.0.1", which contradicts the very next bullet saying releases/latest excludes prereleases and still resolves to 21.1.0. Both statements were true of different checks; the sentence conflated them. Split: 21.1.0 verified through releases/latest (the live path today), 22.0.1 verified by building the same URL from its tag, as the post-promotion target. Also took the reviewer's alternative suggestion in addition to the link fix, since they are not redundant: the badge is a hardcoded versioned MSI URL that follows nothing and goes stale silently, so keeping it current is now a release-prep checklist item rather than something to rediscover by review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jqnatividad
added a commit
that referenced
this pull request
Aug 10, 2026
…orkflow to strip the portable binaries from its zip (#4382) * docs(changelog): file the post-tag packaging work under 22.0.1, not Unreleased The 22.0.1 release is still a pre-release, and fifteen commits of packaging and publishing work landed after the tag to make it shippable. Both changelogs filed that work under `[Unreleased]` — the section directly above `[22.0.1]` — so it read as coming *after* the release it actually ships in. Move those entries into each file's 22.0.1 section under a new "Packaging & Publishing (post-tag)" subsection. The tag stays at 0dbd4c5, so the preamble says plainly that these landed after it rather than implying the tag contains them, and notes that a per-target asset refresh is still in flight — the x86_64-linux-gnu zip predates the portable-binary removal by about six hours and still contains them. Also corrects the synthesize/profile target list, which was wrong in both directions: it claimed the x86_64 Windows targets had `profile` (they have neither `profile` nor `synthesize`) and omitted aarch64-apple-darwin (which has both, via the self-hosted macOS ARM64 workflows — publish.yml's entry for that target is commented out). Verified against each publish workflow's feature list. The 21.1.0...22.0.1 compare link only covers the tag, so a second SHA-pinned link is added for the post-tag range rather than a moving master ref. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: correct two stale viz_static claims that #4368 invalidated #4368 dropped viz_static from the Windows publish targets, which left two places asserting the opposite. CHANGELOG.md's 22.0.1 viz entry claimed "Windows and macOS now compile the viz_static tier". That is false for Windows at HEAD — rust-windows.yml builds `...,ui,viz` only, because adding viz_static pushed the job from ~27min past its 90min timeout without ever reaching the tests. The entry contradicted the "Windows prebuilts: viz yes, viz_static no" note in its own section. Narrowed to macOS, with the Windows revert and its reason spelled out. rust-windows.yml's own comment still justified itself with "Windows ships viz_static in publish.yml" — the premise #4368 removed. The conclusion (no Windows viz_static coverage) happens to still hold, so only the reasoning is rewritten; the cargo test invocation is untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: add a manual workflow to strip portable binaries from a published zip #4380 stopped publishing the portable binaries (qsvp, qsvplite, qsvpdp, qsvpmcp), but assets uploaded before it landed still contain them — the 22.0.1 x86_64-unknown-linux-gnu zip among them. Rebuilding a target to drop four files is disproportionate: that zip already postdates #4371/#4372/#4373, so the portable binaries are its only discrepancy, and a rebuild would emit non-deterministic PGO output differing from what users already downloaded. The unsign -> modify -> re-sign flow mirrors macOS-arm64-selfhosted-publish-qsvpy.yml, which already rewrites a published pre-release zip to add qsvpy binaries. Scoped to x86_64-unknown-linux-gnu, with no target input, and running on ubuntu-24.04 to match the runner publish.yml builds that artifact on — glibc is forward incompatible, so a binary built on 24.04 will not start on 22.04 and the smoke test would fail on a good archive. The post-delete assertion requires qsv + qsvlite + qsvdp + qsvmcp to survive, which holds only for this target: publish-target.yml skips qsvdp for aarch64-unknown-linux-gnu and all non-Linux targets, and skips qsvmcp for musl and aarch64-unknown-linux-gnu. The header records how to extend it properly. Deletes with `7z d -tzip`, not Info-ZIP `zip -d`: publish-target.yml creates these archives with `7z a -tzip`. Entries are resolved by exact basename rather than a glob — a `qsv*` prefix match would take `qsv` itself with them. Trust model, because this replaces a published artifact: - the DOWNLOADED asset is verified against the release tag's public key before anything touches it. Without that anchor the workflow would strip whatever signature the asset carried (or none) and apply a fresh valid one, laundering a tampered artifact into a signed release asset — every later check only attests to the signature this workflow itself created - the public key comes from the tag (`git show "$TAG:src/qsv-zipsign-public.key"`), not the branch: that is the key clients of that release embed via include_bytes!, so a rotation surfaces as a failure instead of false confidence - the private key exists for exactly three steps — fetch, sign, shred. It is materialized after checkout, after the third-party downloader action, and after all archive manipulation, and destroyed before any archive content executes. persist-credentials: false keeps a git credential out of the same window - the signature is re-verified after the smoke test, immediately before upload, so the bytes published are the bytes attested - the tag must resolve to MAJOR.MINOR.PATCH, exist, and still be a PRERELEASE. Replacing an asset changes its sha256 and a stable release is likelier to have been pinned. Fails closed on an empty or unexpected isPrerelease value - dry_run defaults to true; upload is the only gated step - finding no portable binaries is a hard error, so an already-stripped archive is never re-signed and re-uploaded unchanged - every keeper must survive and every victim must be gone before signing - zipsign signs bytes, not zip structure, so a corrupted central directory could still verify: the rewritten qsv is extracted and run - a constant concurrency group: it is evaluated from the raw input before the tag is resolved, so keying it on release_tag would let an auto-detect run and an explicit run for the same tag race anyway Validated end-to-end locally with a throwaway zipsign keypair against a 7z-created archive: an authentic asset completes the full flow, while a tampered asset and an unsigned asset are both refused at the trust anchor, before any unsign. Also covers the flat, nested and already-stripped layouts, and the prerelease guard against prerelease/stable/missing/empty outcomes. Note zipsign salts signatures with --context, defaulting to the input filename, so the asset name must stay byte-identical throughout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 premise no longer holds
The portable subvariants (
qsvp,qsvplite,qsvpdp,qsvpmcp) existed as a SIGILLfallback — README described them as "compiled without any CPU features enabled."
-C target-cpu=nativewas removed from the x86_64 publish targets infd74f8f0cprecisely because it caused SIGILL on GitHub runners, so the regular prebuilts have been
equally portable ever since. Today
target-cpuappears in exactly one place —.cargo/config.toml's[target.aarch64-apple-darwin]— and there is no[build]section.publish-portable.ymlbuilt no macOS target, so across all five targets it did build,there was no CPU-feature difference at all.
The shipped artifacts confirm it
Central directory of
qsv-22.0.1-x86_64-unknown-linux-gnu.zip(read via an HTTP rangerequest on the tail — no 479 MB download):
qsvliteqsvpliteqsvdpqsvpdpqsvmcpqsvpmcpqsvqsvpMatching compressed sizes to the byte is near-conclusive for identical content. Only
qsvpdiffered fromqsv, and only becauseqsvis PGO-optimized whileqsvpwas not —an optimization difference, not a portability one. The duplication added ~330 MB to that
archive, ~255 MB of it byte-identical copies of binaries already inside it.
Not a regression
qsvp.exeis already absent from 22.0.1's Windows MSVC zip: portable's Windows legsbuild
qsvmcpfrom the release tag and so hit the sameviz_staticstall (#4376) that keptWindows from publishing at all. This change makes an existing state explicit rather than
creating one.
Docs
README:167— SIGILL-fallback paragraph rewritten: the x86_64 prebuilts are already portable.README:305— Variants note now explains the subvariants are no longer published, and why.README:169— Easy-installer paragraph de-qsvp'd, plus an[!IMPORTANT]note (see below).README:165— deliberately left alone. Itstarget-cpu=nativeclaim for Apple Silicon,Windows ARM, ppc64le and s390x was verified accurate — each of those workflows does set it.
### Removedentry carrying the table above.publish-target.ymlandrust-linux-arm64.yml.Follow-up in an external repo — a deadline, not an outage
dathere/qsv-easy-windows-installerextractsqsvp.exeand must be repointed atqsv.exe.An earlier revision of this PR claimed it was already broken. That was wrong, and the
correction came from the installer-side session, verified independently on both sides:
api.github.com/repos/dathere/qsv/releases/latest, which excludesprereleases. 22.0.1 is published with
prerelease=true, so that endpoint resolves to21.1.0 — whose Windows MSVC zip does contain
qsvp.exe. The installer works today.The break lands when the first stable release ships without
qsvp.exe— i.e. when22.0.1 is promoted, or at the next stable tag. Merging this PR does not itself break anyone.
Also surfaced by that session, and worth recording here:
(
src-tauri/src/lib.rs:34,zip.by_name("qsvp.exe")); its destination is alreadyqsv.exe. The fix is that one string.bin dir to
HKCU\Environment\Path. Noting it so nobody assumes downstream installersverify our signatures; qsv's docs make no such claim, and this PR does not add one.
.name) and interpolates it into the download URL as if itwere the tag. That works only because qsv titles releases with the bare version — verified
true for all 8 most recent releases.
Both release-time constraints this repo owns (keep title == tag; don't promote out of
prerelease before the installer ships) are now recorded in
.claude/skills/release-prep.Validation
actionlintreports 0 findings on every touched file;scripts/check-publish-matrix-sync.pyOK;scripts/docs-drift-check.pyOK; the#variantsand
#installation-optionsanchors were verified to exist.Note:
publish-qsvpy.ymlhas 12 pre-existing actionlint findings on master (shellcheckerrors and an undefined
musl-prepkey). Untouched here — flagging so they aren't read asfallout from this change.
🤖 Generated with Claude Code