You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Download page: latest releases ship no assets, filename pattern is wrong, and the Sparkle update claim is unsubstantiated #253
Writing the rewrite of codevetter.com/download surfaced three claims on the live page that do not currently hold. Filing together because they share one likely root cause: the cutover to the native SwiftUI release pipeline.
1. The six newest releases ship no downloadable build
v1.12.0 through v1.13.2 — six releases cut between 2026-09-04 23:11Z and 2026-09-05 11:10Z — all have zero release assets.
The newest release carrying a DMG is v1.11.1 (2026-09-03). Every release from v1.5.3 onward carries four assets, so this is a regression rather than intent.
The /download page's "Latest release →" link therefore sends visitors to a release with nothing to download.
PROJECT_STATUS.md:43 describes auto-release.yml cutting a v<version> tag on Shared.xcconfig version bumps while release.yml signs, notarizes, qualifies and uploads. That split is consistent with tags being cut while the upload job does not complete.
2. The documented filename pattern matches no published asset
/download states the artifact is CodeVetter-<version>-arm64.dmg.
Every published asset is CodeVetter_<version>_aarch64.dmg — underscores, and aarch64 rather than arm64. Anyone scripting against the documented name gets nothing.
3. The Sparkle auto-update claim is contradicted by the repo's own evidence
/download states installed copies "update through the in-app Sparkle appcast".
https://codevetter.com/appcast.xml → 404
evidence/verification/native-release-readiness-2026-09-02.md:28 — "no production HTTPS Sparkle appcast is configured"
crates/codevetter-core/src/capabilities.rs:298 — "Sparkle is locally packaged but remains disabled until production signing, appcast, EdDSA, and installed-upgrade gates pass"
crates/codevetter-core/src/capabilities.rs:666 — "Sparkle remains inactive unless a production HTTPS appcast and EdDSA public key are present"
On the evidence available, auto-update is not live. I could not disprove that it was enabled in the days since 2026-09-02 — there is no appcast at the conventional URL and nothing in the repo points at another one. Someone with the deploy view should confirm.
This is the most expensive of the three to leave standing: a download page asserting an update mechanism that the product's own verification evidence says is disabled sits badly against "Review suggests. Evidence decides."
Suggested fixes
Make release.yml fail loudly when a tag is cut without assets uploaded, so this cannot regress silently again.
Correct the filename pattern on /download, or template it from the release feed.
Either enable the production appcast or change the page to say updates are manual for now.
Template the version string on /download rather than hardcoding — six releases landed in under 13 hours.
Found while drafting SEO/GEO content against the live surface. Nothing has been published; the draft is held pending these.
Took the two mechanical parts of this. #259 is merged (all 22 checks green, squashed as 14ee910e).
What is fixed
Item 1 — the pipeline can no longer report success without shipping a build.
The root cause was upstream of release.yml: auto-release.yml created the published release before any build ran and exited without waiting, so the release existed the moment the tag was cut and the run went green regardless of what happened next. All eight recent auto-release.yml runs reported success, including the six that produced asset-less releases. The only asset check lived inside release.ymlafter a successful upload, and scripts/verify-release-manifest.mjs was referenced by no workflow and no package script at all.
Three changes:
the release is created as a draft on an explicitly created tag, so a failed build leaves nothing on /releases/latest, in the release feed, or on the download page;
verify-release-manifest.mjs is rewritten as the required gate and now runs twice — in release.yml before the draft is flipped to published, and again in auto-release.yml after the build. It fails when a contract asset is missing, empty, still uploading, or attached under a name that is not on the contract;
auto-release.ymlwaits on the dispatched release.yml run, so a green auto-release run means a downloadable release exists.
Signing, notarization, secrets, and both workflows' triggers are untouched.
Items 2 and 3 — the page no longer holds claims that can drift.
/download and /download.md read the tag, the installer filename, the update archive, and appcast presence from the GitHub release feed at build time. The filename is no longer written down anywhere on the page, so it cannot be wrong; the version is no longer hardcoded; and the "Latest release" button points at the newest release that actually carries an installer rather than at v<MARKETING_VERSION>. If the feed is unreachable the build falls back to claiming nothing.
The Sparkle claim is now conditional on a published appcast.xml. None exists, so both surfaces currently read:
Installed copies do not update themselves yet: no published release carries the signed Sparkle appcast the in-app updater requires, so update by downloading the newest release.
Live page today: Download v1.11.1 →, CodeVetter_1.11.1_aarch64.dmg.
On your open question — the appcast is real, it just has never been published. native-production-qualification.yml generates it with Sparkle's generate_appcast, EdDSA-signs it, and cryptographically inspects it; release.yml uploads it as a release asset. The feed URL the app is built with is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not codevetter.com/appcast.xml — so the 404 you found is expected and is not the feed. Nothing was invented: the page states the mechanism is inactive, and it will turn itself back on the first time a release publishes an appcast.
What a maintainer must do to prove it
Everything above is verified against fixtures and against the existing release feed, but the pipeline itself is still unproven end to end — no run has ever reached the upload step. As #252 notes, each run since the native cutover failed at a different, later gate. The next release attempt is the test, and only a maintainer can run it.
To prove it:
Bump MARKETING_VERSION in apps/macos/Config/Shared.xcconfig to the next patch and merge to main.
Watch the single auto-release.yml run. It now blocks until release.yml finishes, so its conclusion is the whole answer.
Green means: the release left draft state, carries exactly CodeVetter-<version>-arm64.dmg, CodeVetter-<version>-arm64.zip, and appcast.xml, and the public appcast URL resolves. The release-manifest-verification artifact on the release.yml run holds the receipt.
Red means the release is still a draft and nothing went public. Read the watched release.yml run — the failing gate names itself. Re-running auto-release.yml will no-op on the existing draft, which is deliberate.
Then redeploy the landing page (gh workflow run deploy-landing.yml --ref main) so /download picks up the new release and flips the update copy to the Sparkle wording on its own.
Worth deciding separately: the six asset-less releases v1.12.0..v1.13.2 are still public and v1.13.2 is still what /releases/latest resolves to. Deleting those six releases (keeping or dropping the tags) would make /releases/latest honest immediately, ahead of the next release. I have not touched them.
Leaving this open — the download-page copy and the gate are both in, but the first claim in this issue stays literally true until a release publishes assets. Close it on the run in step 2.
Ran the proof in your step 2. The gate works — it failed closed, and nothing went public. Assets still did not publish, so this issue stays open; the failure is #252's gate, not the release gate.
the release is still a draft (draft=true, 0 assets), reachable only from the authenticated list — it has no public tag page yet, it renders as untagged-15c25cc88390e82bfae3;
/releases/latest is unchanged at v1.13.2 — the failed attempt added nothing to the release feed and nothing to /download;
auto-release.yml reported failure, not success. That is the specific defect this issue opened on: the previous version of this workflow would have reported success here, exactly as it did for the six asset-less releases.
So the half of the contract that can be proven by a red run is proven. The half that needs a green run is not, because the build never reached the upload step.
Why the build failed
Not signing, not notarization, not Sparkle, not a secret — all of those passed. Step 17, the installed-upgrade proof, failed at the first GUI launch of the retained Tauri incumbent:
Application 62867 did not show a visible window within 25000 ms
Full analysis in #252, which is now the sole remaining blocker: its original unknown command rubrics cause is fixed — the v1.11.1 pin seeds the rubric correctly and the gate advanced two steps past it before failing on window registration.
I left the draft release and the v1.13.3 tag exactly as the pipeline left them, and did not publish or delete either.
On the six asset-less releases
Listed, not touched:
Tag
Published
Assets
v1.13.2
2026-09-05 11:10Z
0
v1.13.1
2026-09-05 10:44Z
0
v1.13.0
2026-09-05 10:34Z
0
v1.12.2
2026-09-04 23:50Z
0
v1.12.1
2026-09-04 23:32Z
0
v1.12.0
2026-09-04 23:11Z
0
Recommendation: delete the six releases, keep the six tags. An asset-less release page is a dead end for anyone who lands on it from the releases index, and v1.13.2 being /releases/latest makes the newest thing we advertise the emptiest. Keeping the tags costs nothing, keeps those commits reachable, keeps git describe honest, and keeps this incident verifiable. Nothing depends on those six releases existing: the qualification pins v1.11.1 as the incumbent, and src/data/release.ts already skips releases with no installer, so /download behaves identically before and after.
Not urgent either way — it stops mattering the moment a release publishes assets, which is the same event that closes this issue.
Blocker (2026-09-06): in-progress: agent — needs a maintainer to cut the next release and watch auto-release.yml go green end to end; the fix is merged and waiting on that real run.
Correcting the root cause on this issue, because the earlier reading was wrong in a way that matters.
release.yml was firing.auto-release.yml already dispatched it explicitly with gh workflow run — the anti-recursion safeguard was handled long before #259. All six broken releases did dispatch release.yml, and all six did fail in the build. The regression was never a missing trigger; it was the qualify job failing, which skips publish, which is why no assets were ever attached.
The real cause: the last gate before upload has never passed, on any machine.scripts/qualify-native-installed-upgrade.mjs asks System Events for count windows of first process whose unix id is <pid>. whose binds to the nearest preceding collection, so that filters windows by unix id — a property only processes carry. Every poll answers -1728 "Can't get unix id of window", the catch swallows it as "not registered yet", and the launch times out unconditionally.
Reproduced locally, both forms polled against the same live pid: the current form errors on every poll for 40s; the parenthesized form returns a count, and returns 1 against an app that genuinely has a window.
Why it hid for six releases. The proof runs only in native-production-qualification.yml, which runs only on a release. So each release cut surfaced exactly one more latent failure rather than all of them:
Release
Run
Failed at
v1.12.1
33929885965
step 13 — rebuild archives
v1.12.2
33930948063
step 15 — Sparkle appcast
v1.13.2
33962630311
step 17 — rubrics missing from the v1.11.0 incumbent
v1.13.3
34029077434
step 17 — this bug
Fix: #263. It also makes the failure legible — the run above produced 25 seconds of identical -1728 lines and nothing else — by retaining the app's output, separating "could not read the process" from "reported no windows", and giving a cold GUI launch on a loaded runner more than 25s.
Keeping this open: assets are not live yet. v1.13.3 is currently a draft with zero assets, which is the #259 gate correctly refusing to publish an assetless release — a real improvement over v1.12.0–v1.13.2, which published as Latest with nothing attached.
The public download mismatch is fixed. codevetter.com/download and /download.md now serve v1.13.7, matching the published DMG and ZIP names. The checked landing build is f051219; Pages fc868277-6431-4f8d-b246-e2ecbd596bf0 and Worker ef571fd9-51ac-4559-91fb-c21339cd650b are deployed, with the Worker at 100% traffic.
Downloaded release run 34095571484 evidence confirms Developer ID notarization/Gatekeeper acceptance, configured HTTPS GitHub Releases appcast, EdDSA signature verification, and isolated installed upgrade/relaunch/rollback with rubric preservation. The public ZIP digest matches the qualified archive. The configured feed is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not the old site-root URL.
Remaining: the appcast receipt explicitly excludes installed updater behavior. Keep this issue open until the actual in-app Sparkle download/install/relaunch path is exercised, or public copy is narrowed to the verified update availability. Manual hosted replacement is not proof of Sparkle installation.
The patched website is now deployed from merged main e6a3b44 after all six deploy gates passed. Pages preview: https://961fb962.codevetter.pages.dev. Worker version ab4401c1-9768-4c12-8d19-c35fb7977acb is assigned 100% traffic in deployment c10d29e4-9ebb-4948-a3f4-9a262b291036, with the exact source SHA tag verified. Live /download and /download.md return 200 and point to CodeVetter-1.13.8-arm64.dmg; /docs/ and /privacy also return 200. The configured GitHub appcast returns 200 with version 1.13.8 and an EdDSA signature. Actual in-app Sparkle download/install/relaunch remains unqualified; this issue stays open for that remaining behavior.
Disk-image checksum verification passes. Mounted read-only without opening Finder or launching the app.
DMG app passes strict deep codesign verification and Gatekeeper assessment (Notarized Developer ID).
App executable, bundled CLI and Info.plist hashes match the independently verified public ZIP.
The disk image contains CodeVetter.app only: there is no Applications shortcut. Manual installation requires opening Applications separately. This is a packaging polish limitation, not evidence of a failed app installation.
Task-owned disk image was detached successfully. Owner installation and data were not modified.
PR #279 now builds complete website/docs output with actual 1.13.9 asset names and explicit manual-update guidance. CI is still running; deployment remains pending.
Resolved the three download-page defects with published-package and live-site evidence.
v1.13.9 is public with DMG, ZIP and signed appcast. The actual public DMG/ZIP digests, app signature and Gatekeeper checks pass; real packaged Codex checks distinguish passing correctness, genuine failure and unavailable performance evidence.
Website deployed to Pages https://90936fbc.codevetter.pages.dev and Worker version 45c3f6e6-9021-4801-b3bb-bde6bf6fda77. Active Worker traffic is 100% with the full merged SHA tag.
Live /download and /download.md both name CodeVetter-1.13.9-arm64.dmg and explicitly say in-app installation/relaunch are still being verified. /docs/ and /privacy respond successfully.
This implements the issue's suggested manual-update alternative. Actual Sparkle installation is NOT claimed qualified: the unresolved requirement remains explicitly retained in README under Verify installed Sparkle updates. The missing Applications shortcut in the DMG is also a known packaging polish limitation; users can open Applications separately. Closing the original broken-download/misleading-claim issue, not asserting whole-product production readiness.
Activity
fix: drop setup-node from the release workflows (zizmor cache-poisoning)
fix: detect an existing draft release by tag list, not gh release view
sarthakagrawal927 commentedon Sep 6, 2026
Took the two mechanical parts of this. #259 is merged (all 22 checks green, squashed as
14ee910e).What is fixed
Item 1 — the pipeline can no longer report success without shipping a build.
The root cause was upstream of
release.yml:auto-release.ymlcreated the published release before any build ran and exited without waiting, so the release existed the moment the tag was cut and the run went green regardless of what happened next. All eight recentauto-release.ymlruns reportedsuccess, including the six that produced asset-less releases. The only asset check lived insiderelease.ymlafter a successful upload, andscripts/verify-release-manifest.mjswas referenced by no workflow and no package script at all.Three changes:
/releases/latest, in the release feed, or on the download page;verify-release-manifest.mjsis rewritten as the required gate and now runs twice — inrelease.ymlbefore the draft is flipped to published, and again inauto-release.ymlafter the build. It fails when a contract asset is missing, empty, still uploading, or attached under a name that is not on the contract;auto-release.ymlwaits on the dispatchedrelease.ymlrun, so a green auto-release run means a downloadable release exists.Signing, notarization, secrets, and both workflows' triggers are untouched.
Items 2 and 3 — the page no longer holds claims that can drift.
/downloadand/download.mdread the tag, the installer filename, the update archive, and appcast presence from the GitHub release feed at build time. The filename is no longer written down anywhere on the page, so it cannot be wrong; the version is no longer hardcoded; and the "Latest release" button points at the newest release that actually carries an installer rather than atv<MARKETING_VERSION>. If the feed is unreachable the build falls back to claiming nothing.The Sparkle claim is now conditional on a published
appcast.xml. None exists, so both surfaces currently read:Live page today: Download v1.11.1 →,
CodeVetter_1.11.1_aarch64.dmg.On your open question — the appcast is real, it just has never been published.
native-production-qualification.ymlgenerates it with Sparkle'sgenerate_appcast, EdDSA-signs it, and cryptographically inspects it;release.ymluploads it as a release asset. The feed URL the app is built with ishttps://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, notcodevetter.com/appcast.xml— so the 404 you found is expected and is not the feed. Nothing was invented: the page states the mechanism is inactive, and it will turn itself back on the first time a release publishes an appcast.What a maintainer must do to prove it
Everything above is verified against fixtures and against the existing release feed, but the pipeline itself is still unproven end to end — no run has ever reached the upload step. As #252 notes, each run since the native cutover failed at a different, later gate. The next release attempt is the test, and only a maintainer can run it.
To prove it:
MARKETING_VERSIONinapps/macos/Config/Shared.xcconfigto the next patch and merge tomain.auto-release.ymlrun. It now blocks untilrelease.ymlfinishes, so its conclusion is the whole answer.CodeVetter-<version>-arm64.dmg,CodeVetter-<version>-arm64.zip, andappcast.xml, and the public appcast URL resolves. Therelease-manifest-verificationartifact on therelease.ymlrun holds the receipt.release.ymlrun — the failing gate names itself. Re-runningauto-release.ymlwill no-op on the existing draft, which is deliberate.Then redeploy the landing page (
gh workflow run deploy-landing.yml --ref main) so/downloadpicks up the new release and flips the update copy to the Sparkle wording on its own.Worth deciding separately: the six asset-less releases
v1.12.0..v1.13.2are still public andv1.13.2is still what/releases/latestresolves to. Deleting those six releases (keeping or dropping the tags) would make/releases/latesthonest immediately, ahead of the next release. I have not touched them.Leaving this open — the download-page copy and the gate are both in, but the first claim in this issue stays literally true until a release publishes assets. Close it on the run in step 2.
Gate releases on a verified asset manifest; source download claims fr…
release: cut 1.13.3 to prove the gated release pipeline (#261)
sarthakagrawal927 commentedon Sep 6, 2026
Ran the proof in your step 2. The gate works — it failed closed, and nothing went public. Assets still did not publish, so this issue stays open; the failure is #252's gate, not the release gate.
The run
9dacf597(PR release: cut 1.13.3 to prove the gated release pipeline #261, 23 checks green),MARKETING_VERSION1.13.2 → 1.13.3 withCURRENT_PROJECT_VERSIONand the three crates moving with it.What the new gate did, step by step
v1.13.3→9dacf597Every claim in your step 4 held:
draft=true, 0 assets), reachable only from the authenticated list — it has no public tag page yet, it renders asuntagged-15c25cc88390e82bfae3;/releases/latestis unchanged atv1.13.2— the failed attempt added nothing to the release feed and nothing to/download;auto-release.ymlreported failure, not success. That is the specific defect this issue opened on: the previous version of this workflow would have reported success here, exactly as it did for the six asset-less releases.So the half of the contract that can be proven by a red run is proven. The half that needs a green run is not, because the build never reached the upload step.
Why the build failed
Not signing, not notarization, not Sparkle, not a secret — all of those passed. Step 17, the installed-upgrade proof, failed at the first GUI launch of the retained Tauri incumbent:
Full analysis in #252, which is now the sole remaining blocker: its original
unknown command rubricscause is fixed — thev1.11.1pin seeds the rubric correctly and the gate advanced two steps past it before failing on window registration.I left the draft release and the
v1.13.3tag exactly as the pipeline left them, and did not publish or delete either.On the six asset-less releases
Listed, not touched:
v1.13.2v1.13.1v1.13.0v1.12.2v1.12.1v1.12.0Recommendation: delete the six releases, keep the six tags. An asset-less release page is a dead end for anyone who lands on it from the releases index, and
v1.13.2being/releases/latestmakes the newest thing we advertise the emptiest. Keeping the tags costs nothing, keeps those commits reachable, keepsgit describehonest, and keeps this incident verifiable. Nothing depends on those six releases existing: the qualification pinsv1.11.1as the incumbent, andsrc/data/release.tsalready skips releases with no installer, so/downloadbehaves identically before and after.Not urgent either way — it stops mattering the moment a release publishes assets, which is the same event that closes this issue.
content(download): rewrite /download with corrected claims
sarthakagrawal927 commentedon Sep 6, 2026
Blocker (2026-09-06): in-progress: agent — needs a maintainer to cut the next release and watch auto-release.yml go green end to end; the fix is merged and waiting on that real run.
sarthakagrawal927 commentedon Sep 6, 2026
Correcting the root cause on this issue, because the earlier reading was wrong in a way that matters.
release.ymlwas firing.auto-release.ymlalready dispatched it explicitly withgh workflow run— the anti-recursion safeguard was handled long before #259. All six broken releases did dispatchrelease.yml, and all six did fail in the build. The regression was never a missing trigger; it was thequalifyjob failing, which skipspublish, which is why no assets were ever attached.The real cause: the last gate before upload has never passed, on any machine.
scripts/qualify-native-installed-upgrade.mjsasks System Events forcount windows of first process whose unix id is <pid>.whosebinds to the nearest preceding collection, so that filters windows byunix id— a property only processes carry. Every poll answers-1728 "Can't get unix id of window", thecatchswallows it as "not registered yet", and the launch times out unconditionally.Reproduced locally, both forms polled against the same live pid: the current form errors on every poll for 40s; the parenthesized form returns a count, and returns
1against an app that genuinely has a window.Why it hid for six releases. The proof runs only in
native-production-qualification.yml, which runs only on a release. So each release cut surfaced exactly one more latent failure rather than all of them:rubricsmissing from the v1.11.0 incumbentFix: #263. It also makes the failure legible — the run above produced 25 seconds of identical
-1728lines and nothing else — by retaining the app's output, separating "could not read the process" from "reported no windows", and giving a cold GUI launch on a loaded runner more than 25s.Keeping this open: assets are not live yet.
v1.13.3is currently a draft with zero assets, which is the #259 gate correctly refusing to publish an assetless release — a real improvement over v1.12.0–v1.13.2, which published asLatestwith nothing attached.content(download): rewrite /download with corrected claims (#262)
fix(release): launch the upgrade proof's apps through LaunchServices (#…
release: cut 1.13.4 with the fixed upgrade-proof launch probe (#266)
sarthakagrawal927 commentedon Sep 8, 2026
The public download mismatch is fixed. codevetter.com/download and /download.md now serve v1.13.7, matching the published DMG and ZIP names. The checked landing build is f051219; Pages fc868277-6431-4f8d-b246-e2ecbd596bf0 and Worker ef571fd9-51ac-4559-91fb-c21339cd650b are deployed, with the Worker at 100% traffic.
Downloaded release run 34095571484 evidence confirms Developer ID notarization/Gatekeeper acceptance, configured HTTPS GitHub Releases appcast, EdDSA signature verification, and isolated installed upgrade/relaunch/rollback with rubric preservation. The public ZIP digest matches the qualified archive. The configured feed is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not the old site-root URL.
Remaining: the appcast receipt explicitly excludes installed updater behavior. Keep this issue open until the actual in-app Sparkle download/install/relaunch path is exercised, or public copy is narrowed to the verified update availability. Manual hosted replacement is not proof of Sparkle installation.
sarthakagrawal927 commentedon Sep 8, 2026
The patched website is now deployed from merged main e6a3b44 after all six deploy gates passed. Pages preview: https://961fb962.codevetter.pages.dev. Worker version ab4401c1-9768-4c12-8d19-c35fb7977acb is assigned 100% traffic in deployment c10d29e4-9ebb-4948-a3f4-9a262b291036, with the exact source SHA tag verified. Live /download and /download.md return 200 and point to CodeVetter-1.13.8-arm64.dmg; /docs/ and /privacy also return 200. The configured GitHub appcast returns 200 with version 1.13.8 and an EdDSA signature. Actual in-app Sparkle download/install/relaunch remains unqualified; this issue stays open for that remaining behavior.
sarthakagrawal927 commentedon Sep 9, 2026
Additional public installer verification for v1.13.9:
PR #279 now builds complete website/docs output with actual 1.13.9 asset names and explicit manual-update guidance. CI is still running; deployment remains pending.
sarthakagrawal927 commentedon Sep 9, 2026
Resolved the three download-page defects with published-package and live-site evidence.
This implements the issue's suggested manual-update alternative. Actual Sparkle installation is NOT claimed qualified: the unresolved requirement remains explicitly retained in README under Verify installed Sparkle updates. The missing Applications shortcut in the DMG is also a known packaging polish limitation; users can open Applications separately. Closing the original broken-download/misleading-claim issue, not asserting whole-product production readiness.