描述
Problem
The release pipeline currently triggers full multi-platform binary builds for every release, even when no Rust source code changed. This wastes CI resources and pollutes the release history with identical binaries.
Example: v0.20.1 (2026-02-17)
PR #161 (fix: install to ~/.local/bin) changed only install.sh + README.md — zero Rust code changes. Yet it triggered:
- 5 platform builds (macOS Intel/ARM, Linux Intel/ARM, Windows)
- DEB package build
- RPM package build
- Homebrew formula update
- Version bump in
Cargo.toml+Cargo.lock
The resulting binaries are functionally identical to v0.20.0 (only the embedded version string differs).
Impact
On Feb 17 alone, 4 releases were created (v0.19.0 → v0.21.1). At ~35 CI minutes per release (7 build jobs), that's over 2 hours of CI time. At least one release (v0.20.1) was entirely unnecessary.
Root Cause
release-please determines version bumps based on conventional commit prefixes only (fix: → patch, feat: → minor). It has no awareness of which files changed. So fix: install to ~/.local/bin triggers a patch release even though no compiled code was affected.
The build-release job in release-please.yml runs unconditionally whenever release_created == 'true':
build-release:
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
uses: ./.github/workflows/release.yml
Proposed Solution
Add a source-code change detection step between release-please and the build job:
check-source-changes:
name: Check if source code changed
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
outputs:
code_changed: ${{ steps.check.outputs.code_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for Rust source changes since last release
id: check
run: |
# Get the previous release tag
CURRENT_TAG="${{ needs.release-please.outputs.tag_name }}"
PREV_TAG=$(git tag --sort=-v:refname | grep -v "$CURRENT_TAG" | head -1)
# Check if any Rust source files changed
CHANGED=$(git diff --name-only "$PREV_TAG"..HEAD -- '*.rs' 'Cargo.toml' 'Cargo.lock' 'build.rs' | head -1)
if [ -n "$CHANGED" ]; then
echo "code_changed=true" >> $GITHUB_OUTPUT
echo "Source code changed since $PREV_TAG"
else
echo "code_changed=false" >> $GITHUB_OUTPUT
echo "No source code changes since $PREV_TAG — skipping binary build"
fi
build-release:
needs: [release-please, check-source-changes]
if: ${{ needs.check-source-changes.outputs.code_changed == 'true' }}
uses: ./.github/workflows/release.yml
This way:
- Code changes (
*.rs,Cargo.toml,build.rs) → full build + release artifacts - Non-code changes (README, install.sh, docs, CI config) → release-please still creates the version tag and changelog entry, but no binary build runs
Alternatives Considered
- Enforce commit prefix discipline (
chore:/docs:for non-code changes) — fragile, relies on contributor discipline - release-please path filtering — not natively supported in v4
- Separate release tracks (code vs docs) — over-engineered for this use case
Additional Improvement
Consider also gating the Homebrew formula update behind the same code_changed check, since there's no point updating the tap when binaries haven't changed.