The signing key moves from job-level to step-level env, so npm install scripts, setup actions and crate build scripts never see it. Linux compiles without the key and signs in a separate tauri bundle step. The legacy rollback builds and signs under a read-only, unpersisted token with the locked Tauri CLI, and a separate job holds the write token to publish.
3.4 KiB
Cutting a release
The release.yml workflow builds and publishes on every pushed plain
vX.Y.Z tag. Pushing a tag with a suffix (vX.Y.Z-rc1) publishes
nothing: the updaters ignore suffixes, so an rc would reach every user
as a stable release. The workflow asserts that the pushed tag matches
Cargo.toml version and fails fast on mismatch, so the order
below matters: bump Cargo before you tag.
Steps for a new version
-
Bump
Cargo.tomlversion(X.Y.Z). -
cargo build --releaselocally to refreshCargo.lock. -
Commit:
chore: bump version to X.Y.Z. -
Tag and push:
git tag -a vX.Y.Z -m "vX.Y.Z" git push origin main git push origin vX.Y.Z -
Watch the "Release" workflow run; it creates the GitHub Release with auto-generated notes and these assets:
claude-code-usage-bubble.exeand its.sig,claude-code-usage-bubble_X.Y.Z_amd64.AppImageand its.sig,claude-code-usage-bubble_X.Y.Z_amd64.deb, andlatest.json. The build jobs need theTAURI_SIGNING_PRIVATE_KEYandTAURI_SIGNING_PRIVATE_KEY_PASSWORDsecrets and a non-emptyplugins.updater.pubkeyintauri.conf.json; the workflow fails before building without them. Only the steps that sign see the key: dependency installs and compilation run without it.
Testing without a real tag
Use the workflow's workflow_dispatch input with a throwaway tag
that carries a suffix, like v0.0.0-test. The suffix keeps the tag
push from starting a publishing run; never use a plain vX.Y.Z tag for
a dry run, because pushing it publishes a release marked latest. The release is created as a draft, so it
does not show up on the public Releases feed or trigger self-updates
for users.
The tag must already exist and point at a commit whose Cargo.toml
version matches it (0.0.0-test here), because the version check also
runs for drafts. Delete the draft and the tag after inspecting it.
Versioning
Semver-ish: bump patch for fixes, minor for features, major for
breaking changes (e.g. settings.json schema change). The in-app
updater compares Version { major, minor, patch } lexicographically,
so a higher tuple wins.
Asset name contract
Legacy clients (v0.5.0 and earlier) read /releases/latest through
the GitHub API and download the asset named
claude-code-usage-bubble.exe (case-insensitive), else the first asset
ending in .exe. The release must therefore carry exactly one .exe
asset, under that name, uploaded unmodified so the GitHub digest
they verify matches. Tauri clients read latest.json, whose URLs must
match the asset names exactly, so the Linux bundles are renamed to
space-free names before upload. scripts/make-latest-json.js enforces
both rules and fails the release otherwise.
Residual risk
The hand-over from a legacy Win32 release to the Tauri exe has been
checked only by reading the updater code at v0.4.0, v0.4.1 and v0.5.0;
it has never run end to end before a release. Static reading cannot
prove runtime behaviour such as antivirus or SmartScreen reactions to
a self-replaced, larger, unsigned exe, WebView2 start-up from a
detached relaunch, or the v0.4.0 helper timing. The first Tauri
release is the first real run. Mitigations: the WebView2 guard, the
--diagnose log, update-error.log, and the release-legacy.yml
rollback, which rebuilds the Win32 sources at a given ref under a
higher version as a Windows-only release. That rollback is itself
unproven end to end.