Files
claude-code-usage-bubble/docs/release-process.md
T
tiennm99 9a9b01ff07 ci: scope the updater signing key to signing steps and split the legacy publish job
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.
2026-10-21 17:29:56 +07:00

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

  1. Bump Cargo.toml version (X.Y.Z).

  2. cargo build --release locally to refresh Cargo.lock.

  3. Commit: chore: bump version to X.Y.Z.

  4. Tag and push:

    git tag -a vX.Y.Z -m "vX.Y.Z"
    git push origin main
    git push origin vX.Y.Z
    
  5. Watch the "Release" workflow run; it creates the GitHub Release with auto-generated notes and these assets: claude-code-usage-bubble.exe and its .sig, claude-code-usage-bubble_X.Y.Z_amd64.AppImage and its .sig, claude-code-usage-bubble_X.Y.Z_amd64.deb, and latest.json. The build jobs need the TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD secrets and a non-empty plugins.updater.pubkey in tauri.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.