Replaces the "build from source only" Install section with a Releases-page download path plus a SmartScreen note, and adds docs/release-process.md so the tag-bump-build flow is captured for future maintainers.
7.7 KiB
phase, title, status, priority, effort, dependencies
| phase | title | status | priority | effort | dependencies |
|---|---|---|---|---|---|
| 1 | Release CI workflow | in_progress | P1 | 2h |
Phase 1: Release CI workflow
Overview
Ship .github/workflows/release.yml. On a pushed tag matching v*,
the workflow verifies the tag matches Cargo.toml's version field
(fail-fast if not), builds cargo build --release on windows-latest,
renames/copies the binary to the exact asset name the updater expects
(claude-code-usage-bubble.exe), and creates a GitHub Release with
that asset attached and auto-generated notes. Also supports
workflow_dispatch for dry-run testing without cutting a real tag.
Requirements
Functional
- Trigger on tag push matching
v*.*.*(andworkflow_dispatchfor testing). - Verify the tag matches
Cargo.tomlversionbefore building. Workflow aborts on mismatch. - Build on
windows-latestwith stable Rust toolchain,x86_64-pc-windows-msvctarget. - Cache cargo registry + target dir to keep wall time under ~5 min.
- Upload
target/release/claude-code-usage-bubble.exeas a Release asset. - Use
gh release createwith--generate-notesfor the body. - Use
--draftonworkflow_dispatchruns so test runs don't become public. - On real tag runs (
vX.Y.Z), publish immediately (not draft).
Non-functional
- Workflow file under ~100 lines.
- No third-party Marketplace actions other than
actions/checkoutandSwatinem/rust-cache(or justactions/cache). Avoidsoftprops/action-gh-release-style wrappers —ghCLI is preinstalled on the runner and is one less supply-chain risk. - Default
GITHUB_TOKENpermissions, with explicitcontents: writeonly on the release job.
Architecture
Flow
push tag v0.1.1
└─→ release.yml (job: build, runs-on: windows-latest)
├─ checkout (at the tag)
├─ Swatinem/rust-cache (uses runner-default stable Rust)
├─ resolve tag (from refs/tags or workflow_dispatch input)
├─ verify Cargo.toml version == tag (strip leading 'v') → abort on mismatch
├─ cargo build --release --locked
├─ gh release create v0.1.1 target/release/claude-code-usage-bubble.exe \
│ --title "v0.1.1" --generate-notes [--draft on workflow_dispatch]
└─ done
Asset name verification
The updater's primary matcher is eq_ignore_ascii_case("claude-code-usage-bubble.exe")
(src/update/release.rs:64). Cargo's name = "claude-code-usage-bubble"
already produces that exe name in target/release/, so no rename is
needed — just upload the file as-is.
Related Code Files
- Create:
.github/workflows/release.yml - Reference (do not modify in this phase):
src/update/release.rs,Cargo.toml
Implementation Steps
-
Create
.github/workflows/release.ymlwith this shape:name: Release on: push: tags: ['v*.*.*'] workflow_dispatch: inputs: tag: description: 'Tag to release (must already exist, e.g. v0.1.1)' required: true permissions: contents: write jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.tag || github.ref }} - uses: Swatinem/rust-cache@v2 - name: Resolve tag id: tag shell: pwsh run: | $tag = if ($env:GITHUB_REF -like 'refs/tags/*') { $env:GITHUB_REF -replace '^refs/tags/','' } else { '${{ github.event.inputs.tag }}' } "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - name: Verify Cargo.toml version matches tag shell: pwsh run: | $tag = '${{ steps.tag.outputs.tag }}' $expected = $tag -replace '^v','' $cargoVersion = (Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"' | Select-Object -First 1).Matches.Groups[1].Value if ($cargoVersion -ne $expected) { Write-Error "Tag ($tag → $expected) does not match Cargo.toml version ($cargoVersion). Bump Cargo.toml before tagging." exit 1 } Write-Host "Cargo version $cargoVersion matches tag $tag" - name: Build release run: cargo build --release --locked - name: Create release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: pwsh run: | $tag = '${{ steps.tag.outputs.tag }}' $asset = 'target/release/claude-code-usage-bubble.exe' $draft = if ('${{ github.event_name }}' -eq 'workflow_dispatch') { '--draft' } else { '' } gh release create $tag $asset --title $tag --generate-notes $draft -
Sanity-check:
gh workflow listafter pushing the file shows the new "Release" workflow. -
Verify the workflow YAML lints clean by viewing it in the GitHub UI (or with
actionlintlocally if installed).
Todo List
.github/workflows/release.ymlwrittenpermissions: contents: writeset- Tag-push trigger and
workflow_dispatchboth wired - Cargo.toml-version-vs-tag check step added and fails on mismatch
- Asset path is
target/release/claude-code-usage-bubble.exeexactly --generate-notesenabled- Committed and pushed to
main
Success Criteria
- Workflow appears under Actions tab on GitHub.
- Manually dispatching with a throwaway tag (
v0.0.0-test) produces a draft release with the.exeattached. - Pushing a tag whose value disagrees with
Cargo.tomlfails the workflow beforecargo buildruns (verify by intentionally mismatching once on a throwaway dispatch). - No third-party actions beyond
actions/checkout@v4andSwatinem/rust-cache@v2. - Workflow file is under ~100 lines including blank lines.
- Out of scope (do not touch):
*.exefallback atsrc/update/release.rs:67-69. Tracked as future cleanup once multi-arch ships.
Risk Assessment
| Risk | Likelihood | Mitigation |
|---|---|---|
Cargo.lock drift causes --locked to fail |
Low | Lockfile is committed; bump it locally before tagging if deps changed |
| Build time >10 min and cache cold | Low | rust-cache covers cargo registry + target/; first run is slow, subsequent fast |
Tag pushed without prior Cargo.toml version bump |
Was Medium → now Mitigated | CI now fails fast in the "Verify Cargo.toml version matches tag" step; maintainer cannot accidentally ship a version-mismatched binary |
gh release create fails because tag does not exist for workflow_dispatch |
Medium | Workflow_dispatch input takes a tag string and actions/checkout is pinned to it — if the tag does not exist, checkout fails fast with a clear error |
Pre-release tag like v0.1.0-rc1 triggers workflow but does not match Cargo's stable version |
Low | Tag-match check uses string equality after ^v strip; 0.1.0-rc1 != 0.1.0 fails fast. If pre-releases are wanted later, change Cargo version and the check still works |
Security Considerations
permissions: contents: writeis the minimum scope needed to create a release; noid-tokenor package perms requested.GH_TOKENis the defaultGITHUB_TOKEN, scoped to this repo only.- No secrets are echoed;
ghreadsGH_TOKENfrom env. - The published
.exeis unsigned. SmartScreen will show "Unknown publisher" the first time a user runs it. Document this in Phase 3; code signing is out of scope.