ci: switch to push-to-main semantic-release pipeline#11
Conversation
Replaces the manual `cargo set-version + git tag + git push --tags` flow with a Conventional-Commits-driven push-to-main pipeline (Flavor A in the gh-release-pipeline skill: Rust CLI without crates.io). How it works: - release-plz watches main; on `feat:`/`fix:` it opens a Release PR that bumps Cargo.toml + writes CHANGELOG.md. - Merging the Release PR creates the v<next> tag and a GitHub Release with the changelog body. - The tag push triggers release.yml, which builds dual-arch macOS tarballs, attaches them + checksums.txt to the existing Release, and opens a PR against uinaf/homebrew-tap to bump Formula/tccutil-rs.rb. Required secrets (documented in CONTRIBUTING.md → Releases): - RELEASE_PLZ_TOKEN: PAT for glitch418x with contents+PR write on this repo - TAP_GITHUB_TOKEN: PAT for glitch418x with contents+PR write on uinaf/homebrew-tap Other changes: - Add license/repository/homepage/publish=false to Cargo.toml metadata - Add `[skip ci]` guard + concurrency group to ci.yml (defense in depth) - Update AGENTS.md key-files list with release pipeline files
There was a problem hiding this comment.
Pull request overview
This PR replaces the manual Rust CLI release process with a Conventional-Commits-driven, push-to-main pipeline using release-plz, while keeping the existing tag-triggered binary build/release workflow and adding an automated Homebrew tap bump.
Changes:
- Add
release-plz.tomland a newrelease-plzworkflow to automate Release PRs and tag/GitHub Release creation. - Update the tag-triggered
release.ymlto preserve release-plz changelog text and bump the Homebrew formula after attaching artifacts. - Harden CI with concurrency + a
[skip ci]guard, and add crate metadata needed for the release automation.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
release-plz.toml |
Configures Conventional-Commits parsing, changelog template, and v<version> tag naming for release-plz. |
Cargo.toml |
Adds package metadata (license, repository, etc.) and publish = false to align with binary-only releases. |
CONTRIBUTING.md |
Documents the new two-stage release flow and required GitHub secrets. |
AGENTS.md |
Adds release pipeline files to the repo “key files” list. |
.github/workflows/release.yml |
Adjusts GitHub Release asset upload behavior and adds Homebrew tap bump step. |
.github/workflows/release-plz.yml |
Introduces push-to-main release-plz automation (release + release PR). |
.github/workflows/ci.yml |
Adds concurrency cancellation and a [skip ci] condition. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # 2. Runs `release-plz update` — opens (or refreshes) a Release PR that | ||
| # bumps Cargo.toml + writes CHANGELOG.md based on Conventional Commits | ||
| # since the last tag. |
There was a problem hiding this comment.
The header comment says the workflow runs release-plz update, but the actual step uses command: release-pr. Update the comment to match the real command to avoid confusion when debugging or changing the pipeline.
| # 2. Runs `release-plz update` — opens (or refreshes) a Release PR that | |
| # bumps Cargo.toml + writes CHANGELOG.md based on Conventional Commits | |
| # since the last tag. | |
| # 2. Runs `release-plz release-pr` — opens (or refreshes) a Release PR | |
| # that bumps Cargo.toml + writes CHANGELOG.md based on Conventional | |
| # Commits since the last tag. |
| - name: Install Rust toolchain (from rust-toolchain.toml) | ||
| run: rustup show | ||
|
|
There was a problem hiding this comment.
These steps are labeled as installing the Rust toolchain, but rustup show only prints the current state and doesn’t ensure the toolchain from rust-toolchain.toml is installed. If release-plz (or its action) needs Cargo/rustc, use a toolchain-setup action (or at least run a command that triggers rustup to install the pinned toolchain); otherwise consider removing/renaming the step to reflect what it does.
| - name: Install Rust toolchain (from rust-toolchain.toml) | ||
| run: rustup show | ||
|
|
There was a problem hiding this comment.
These steps are labeled as installing the Rust toolchain, but rustup show only prints the current state and doesn’t ensure the toolchain from rust-toolchain.toml is installed. If release-plz (or its action) needs Cargo/rustc, use a toolchain-setup action (or at least run a command that triggers rustup to install the pinned toolchain); otherwise consider removing/renaming the step to reflect what it does.
| # Skip the verify suite on bot-authored bump commits (release-plz Release | ||
| # PR merges are not [skip ci] commits, so they still run; this guard is | ||
| # defense-in-depth in case a future automated commit lands on main). | ||
| if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} |
There was a problem hiding this comment.
The job-level condition uses github.event.head_commit.message, but this workflow also runs on pull_request events where head_commit is not present. That can cause the expression to evaluate with null/undefined and fail the workflow. Consider guarding on github.event_name == 'push' (or coalescing to an empty string) before calling contains(...), so PR runs aren't broken.
| if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} | |
| if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }} |
Replaces the release-plz scaffolding from the previous commit on this
branch with semantic-release, matching uinaf/react-json-logic's release
shape per the gh-release-pipeline skill (Flavor A: Rust CLI without
crates.io, version-decider via semantic-release, no cargo-dist).
Pipeline shape (single ci.yml):
push to main
└─► verify (PR + push: fmt + clippy + test + coverage + build)
└─► release (push to main only, !contains [skip ci])
├─► semantic-release: analyze commits, decide version
├─► scripts/release-prepare.sh: bump Cargo.toml + Cargo.lock
├─► @semantic-release/git: commit bump back with [skip ci]
├─► @semantic-release/github: tag + GitHub Release + notes
├─► dual-arch darwin build + tarballs + checksums.txt
├─► softprops/action-gh-release: attach assets to Release
└─► dawidd6/action-homebrew-bump-formula: PR on tap
Bot identity is glitch418x (matching react-json-logic).
Required secrets (CONTRIBUTING.md → Releases):
- GITHUB_TOKEN (provided automatically) for tag + bump-back + Release
- TAP_GITHUB_TOKEN (PAT) for cross-repo Homebrew formula bump
Folded the previously-separate tag-triggered release.yml into the release
job because GITHUB_TOKEN-pushed tags do not trigger downstream workflows
by design — keeping release.yml standalone would never fire.
Also addresses Copilot review on PR #11:
- ci.yml verify gate now coalesces on github.event_name (was breaking on
pull_request events where github.event.head_commit is null, which is why
CI didn't run on this PR after the last commit)
…light Workflow-level `cancel-in-progress: true` overrides the `release` job's job-level `cancel-in-progress: false` guard, because the cancellation happens at the parent workflow run level. If a second push to main arrives while a release is mid-flight, semantic-release may have already created the tag and GitHub Release, but the binary asset upload and Homebrew tap bump would never run — leaving a half-published release. Move the concurrency groups to be job-level: verify stays cancellable, release queues. The skill's own example has this bug; uinaf/react-json- logic does too. Catching it proactively for tccutil since release jobs involve external state (the tap repo) that is harder to recover from. Closes Cursor Bugbot review on PR #11.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4278e44. Configure here.
| - name: Install Rust toolchain (with darwin cross-compile targets) | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| targets: aarch64-apple-darwin,x86_64-apple-darwin |
There was a problem hiding this comment.
Cross-compile targets installed on overridden toolchain
High Severity
dtolnay/rust-toolchain@stable installs cross-compile targets (aarch64-apple-darwin, x86_64-apple-darwin) on the stable toolchain via rustup default, but rust-toolchain.toml (with channel = "1.93") takes precedence per rustup's override order. When cargo build --target x86_64-apple-darwin runs, it uses the 1.93 toolchain, which doesn't have that target installed. On ARM64 macos-latest runners, the aarch64 build would succeed (host target), but the x86_64 cross-compile build would fail. By that point, semantic-release has already created the tag and GitHub Release, leaving a half-published release with no binary assets.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4278e44. Configure here.
|
🎉 This PR is included in version 0.2.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 0.2.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |


Summary
Replace the manual `cargo set-version + git tag + git push --tags` flow with a Conventional-Commits-driven push-to-main pipeline. Mirrors `uinaf/react-json-logic` per the `gh-release-pipeline` skill (Flavor A — Rust CLI without crates.io, semantic-release as version-decider).
How it works
```
push to main
└─► verify (PR + push: fmt + clippy + test + coverage + build)
└─► release (push to main only, !contains [skip ci])
├─► semantic-release: analyze commits, decide version
├─► scripts/release-prepare.sh: bump Cargo.toml + Cargo.lock
├─► @semantic-release/git: commit bump back with [skip ci]
├─► @semantic-release/github: tag + GitHub Release + notes
├─► dual-arch darwin build + tarballs + checksums.txt
├─► softprops/action-gh-release: attach assets to Release
└─► dawidd6/action-homebrew-bump-formula: PR on uinaf/homebrew-tap
```
Bot identity is `glitch418x` (matching `react-json-logic`).
The release job folds binary build + Homebrew bump in. A separate tag-triggered `release.yml` would never fire because tags pushed by `GITHUB_TOKEN` don't trigger downstream workflows by design.
Files
Required secrets
Configure on this repo before merging:
Addresses Copilot review on the previous commit
Test plan
Note
Medium Risk
Changes the release automation to push-to-main
semantic-releasewith automated version bumps, tagging, asset publishing, and Homebrew tap PRs, which can affect release correctness and requires new repo secrets/permissions. Job-level concurrency and[skip ci]gating reduce but don’t eliminate the risk of mis-publishes if misconfigured.Overview
Moves from a tag-triggered
release.ymlto a single.github/workflows/ci.ymlwith a cancellableverifyjob (PRs + pushes) and a queuedreleasejob that runs only onmainpushes.The new release flow runs
semantic-release(configured via.releaserc.json) to compute the next version from Conventional Commits, executesscripts/release-prepare.shto bumpCargo.toml/Cargo.lockand commit back with[skip ci], then builds and attaches dual-arch macOS tarballs pluschecksums.txtand opens a Homebrew tap bump PR.Also adds Rust package metadata (
license,repository,homepage,readme,publish = false) and documents the new release process inCONTRIBUTING.md/AGENTS.md.Reviewed by Cursor Bugbot for commit 4278e44. Bugbot is set up for automated code reviews on this repo. Configure here.