update.json is the file an installed copy fetches from
releases/latest/download/update.json to learn it is behind. It carries a
darwin entry and nothing else, so a Windows install has no update to find
and stays where it is forever.
Both of the last two releases:
$ gh release download v0.3.10 --pattern update.json && jq '.platforms|keys' update.json
["darwin"]
$ gh release download v0.3.9 --pattern update.json && jq '.platforms|keys' update.json
["darwin"]Not a regression — it has presumably been this way since the manifest was added. It is worth fixing now because of what it is currently stranding.
Why it matters right now
v0.3.9's Windows build cannot open a window at all. --system-tray was passed
on every platform, and on Windows Craft reads that as "build a tray
application", logs FailedToCreateWindow and opens nothing:
⚡ Creating system tray application
[2026-10-28T09:14:53Z] ERROR FailedToCreateWindow
no window within 60sThat is fixed in v0.3.10 (b8ad447), and SystemCleaner-0.3.10-windows-x64.zip
is attached to the release. But the updater will not offer it, so anyone on
0.3.9 for Windows is sitting on an app that starts and shows nothing, with no
route out except noticing by themselves and downloading the zip by hand. The
one release where auto-update matters most is the one it cannot deliver.
Cause
scripts/build-desktop-app.ts names a single artifact:
const manifest = createGitHubUpdateManifest({
repository: UPDATE_REPOSITORY,
version: APP_VERSION,
artifacts: { darwin: dmgPath },
})It is not simply a missing key. That script runs in the desktop job, which
needs a real Mac for codesign and notarytool, while the Windows zip is
built in parallel by desktop-windows on a Windows runner. At the moment the
manifest is written, the artifact it would have to describe does not exist on
that machine and may not exist yet at all.
Suggested shape
.github/workflows/release.yml already solved this exact problem one asset
over. The checksums job runs last:
checksums:
name: Checksums
needs: [release, desktop, desktop-windows]
if: ${{ always() && needs.release.result == 'success' }}and rebuilds checksums.txt over whatever actually landed, because two jobs
each attaching their own partial copy meant the last writer won. The manifest
has the same shape of problem and wants the same answer: assemble it once,
last, from the assets actually on the release, rather than from whatever one
builder could see.
Worth checking while in there:
- whether the updater on Windows keys off
platforms.win32,platforms.windowsor something else, so the key written is the key it reads - the
desktopjob is skipped entirely whenDESKTOP_SIGNING_IDENTITY_NAMEis unset, which today also means noupdate.jsonat all; a manifest built inchecksumswould still describe the Windows asset in that case - a release with no signed macOS DMG should not publish a manifest claiming one
Verifying a fix
A fix should make this print both platforms:
$ gh release download <tag> --pattern update.json && jq '.platforms|keys' update.jsonand the sha256 for each should match checksums.txt for the same asset. The
macOS side is correct today and is worth keeping as the reference: in v0.3.10
the DMG hash in update.json matches checksums.txt and the downloaded file
exactly.