From e7204205a1bee3d0ca43ecff199e63a010236339 Mon Sep 17 00:00:00 2001 From: shauneccles Date: Sat, 10 Oct 2026 17:53:49 +1100 Subject: [PATCH 1/6] ci: skip tests and build for release-please PRs and their merges A release bump (changelog, manifest, the package's own version lines in pyproject.toml/uv.lock, plus the __version__ literal) cannot change test outcomes or built artifacts. The 18-cell test matrix and the build job skip it on pull_request and on merges to main; renovate dependency and lock bumps leave foreign anchors or lines and still run everything. --- .github/workflows/publish.yml | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index af9b05e..0973132 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,6 +14,76 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + plan: + name: Plan + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + version_only: ${{ steps.version_scope.outputs.version_only }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + # version-scope diffs PRs against origin/main: merge refs carry no + # parents without full history. + fetch-depth: 0 + - id: version_scope + env: + EVENT: ${{ github.event_name }} + REF: ${{ github.ref }} + run: | + case "$EVENT" in + pull_request) base="origin/main...HEAD" ;; + push) + if [[ "$REF" == refs/tags/* ]]; then exit 0; fi + base="HEAD^" + ;; + *) exit 0 ;; # schedule and manual runs always build + esac + export SCOPE_BASE="$base" + scope=$(python3 - <<'PY' + import os, re, subprocess + + def git(*args: str) -> str: + result = subprocess.run( + ["git", *args], capture_output=True, text=True, timeout=30 + ) + return result.stdout if result.returncode == 0 else "" + + files = set(git("diff", "--name-only", os.environ["SCOPE_BASE"], "HEAD").split()) + if not files: + print("false") # empty or unreadable diff: build everything + raise SystemExit + prose = {"CHANGELOG.md", ".release-please-manifest.json"} + manifests = {"pyproject.toml", "uv.lock"} + rest = files - manifests + if rest and not rest <= prose: + print("false") + raise SystemExit + anchors = ('name="audio-hotplug"', "[package]", "[project]") + for name in sorted(files & manifests): + patch = git("diff", "--unified=0", os.environ["SCOPE_BASE"], "HEAD", "--", name) + if not patch: + print("false") + raise SystemExit + for hunk in re.split(r"^@@", patch, flags=re.MULTILINE)[1:]: + lines = hunk.splitlines() + parts = lines[0].split(maxsplit=3) + anchor = parts[3].lstrip() if len(parts) > 3 else "" + changed = [ + line[1:].lstrip() + for line in lines[1:] + if line[:1] in {"+", "-"} + ] + ok = bool(changed) and all(c.startswith("version") for c in changed) + ok = ok and anchor.replace(" ", "").startswith(anchors) + if not ok: + print("false") + raise SystemExit + print("true") + PY + ) + echo "version_only=$scope" >> "$GITHUB_OUTPUT" lint: name: Lint (prek) runs-on: ubuntu-latest @@ -31,7 +101,8 @@ jobs: prek-version: '0.5.3' test: name: Test (${{ matrix.os }}, Python ${{ matrix.python }}) - needs: lint + needs: [lint, plan] + if: needs.plan.outputs.version_only != 'true' runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: @@ -52,7 +123,8 @@ jobs: - name: Test run: uv run --no-sync pytest -v --cov=audio_hotplug --cov-report=term build: - needs: lint + needs: [lint, plan] + if: needs.plan.outputs.version_only != 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -91,18 +163,24 @@ jobs: if-no-files-found: error ci-passed: name: CI passed - needs: [lint, test, build] + needs: [plan, lint, test, build] if: always() runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Require every check + # Version-only release bumps legitimately skip test/build; lint always runs. + - name: Require lint even for metadata-only changes + if: needs.lint.result != 'success' + run: exit 1 + - name: Require native coverage unless the diff is release metadata + if: needs.plan.outputs.version_only != 'true' env: RESULTS: ${{ toJSON(needs) }} run: | python3 - <<'PY' import json, os - assert all(job['result'] == 'success' for job in json.loads(os.environ['RESULTS']).values()) + results = json.loads(os.environ['RESULTS']) + assert all(v['result'] == 'success' for k, v in results.items() if k != 'plan'), results PY publish-release: name: Publish verified package release From 7be7d23b93d931d7e3a5c981e424c39967d4a8bd Mon Sep 17 00:00:00 2001 From: shauneccles Date: Sat, 10 Oct 2026 19:26:20 +1100 Subject: [PATCH 2/6] ci: use the shared release-ci version-scope action --- .github/workflows/publish.yml | 63 +++++------------------------------ 1 file changed, 8 insertions(+), 55 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0973132..2312bb6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,62 +28,15 @@ jobs: # parents without full history. fetch-depth: 0 - id: version_scope - env: - EVENT: ${{ github.event_name }} - REF: ${{ github.ref }} - run: | - case "$EVENT" in - pull_request) base="origin/main...HEAD" ;; - push) - if [[ "$REF" == refs/tags/* ]]; then exit 0; fi - base="HEAD^" - ;; - *) exit 0 ;; # schedule and manual runs always build - esac - export SCOPE_BASE="$base" - scope=$(python3 - <<'PY' - import os, re, subprocess - - def git(*args: str) -> str: - result = subprocess.run( - ["git", *args], capture_output=True, text=True, timeout=30 - ) - return result.stdout if result.returncode == 0 else "" + uses: LedFx/release-ci/actions/version-scope@442e00705d4e5edd589021851e756761363f9d84 # v0.3.1 + with: + package-name: audio-hotplug + versioned-manifests: | + pyproject.toml + uv.lock + simple-version-files: | + src/audio_hotplug/__init__.py - files = set(git("diff", "--name-only", os.environ["SCOPE_BASE"], "HEAD").split()) - if not files: - print("false") # empty or unreadable diff: build everything - raise SystemExit - prose = {"CHANGELOG.md", ".release-please-manifest.json"} - manifests = {"pyproject.toml", "uv.lock"} - rest = files - manifests - if rest and not rest <= prose: - print("false") - raise SystemExit - anchors = ('name="audio-hotplug"', "[package]", "[project]") - for name in sorted(files & manifests): - patch = git("diff", "--unified=0", os.environ["SCOPE_BASE"], "HEAD", "--", name) - if not patch: - print("false") - raise SystemExit - for hunk in re.split(r"^@@", patch, flags=re.MULTILINE)[1:]: - lines = hunk.splitlines() - parts = lines[0].split(maxsplit=3) - anchor = parts[3].lstrip() if len(parts) > 3 else "" - changed = [ - line[1:].lstrip() - for line in lines[1:] - if line[:1] in {"+", "-"} - ] - ok = bool(changed) and all(c.startswith("version") for c in changed) - ok = ok and anchor.replace(" ", "").startswith(anchors) - if not ok: - print("false") - raise SystemExit - print("true") - PY - ) - echo "version_only=$scope" >> "$GITHUB_OUTPUT" lint: name: Lint (prek) runs-on: ubuntu-latest From 53c6f8f10623c0e931429a8d9c10584ba3e36986 Mon Sep 17 00:00:00 2001 From: shauneccles Date: Sat, 10 Oct 2026 19:31:21 +1100 Subject: [PATCH 3/6] ci: pin version-scope to release-ci v0.4.0 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2312bb6..e9217d5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: # parents without full history. fetch-depth: 0 - id: version_scope - uses: LedFx/release-ci/actions/version-scope@442e00705d4e5edd589021851e756761363f9d84 # v0.3.1 + uses: LedFx/release-ci/actions/version-scope@f6b0dd14fcd45d61d71fbe8e5ec2d3f1588f1b68 # v0.4.0 with: package-name: audio-hotplug versioned-manifests: | From cf0187316c3526173c5f3986c3c004e51cfe5dbb Mon Sep 17 00:00:00 2001 From: shauneccles Date: Sat, 10 Oct 2026 19:48:11 +1100 Subject: [PATCH 4/6] ci: bump version-scope to release-ci v0.4.1 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e9217d5..3e0b55f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: # parents without full history. fetch-depth: 0 - id: version_scope - uses: LedFx/release-ci/actions/version-scope@f6b0dd14fcd45d61d71fbe8e5ec2d3f1588f1b68 # v0.4.0 + uses: LedFx/release-ci/actions/version-scope@b7cd7375d72b5f87c20eb0673a9f0ce6ccb930f4 # v0.4.1 with: package-name: audio-hotplug versioned-manifests: | From 139824e65ee02b1ceabe48bea55c2988b764bb5d Mon Sep 17 00:00:00 2001 From: shauneccles Date: Sat, 10 Oct 2026 20:22:39 +1100 Subject: [PATCH 5/6] ci: bump version-scope to release-ci v0.4.2 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3e0b55f..7d31997 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: # parents without full history. fetch-depth: 0 - id: version_scope - uses: LedFx/release-ci/actions/version-scope@b7cd7375d72b5f87c20eb0673a9f0ce6ccb930f4 # v0.4.1 + uses: LedFx/release-ci/actions/version-scope@12ea4ced7288cb6b0f3a8aafbac3e825969c17ca # v0.4.2 with: package-name: audio-hotplug versioned-manifests: | From c5a4881d56c2533cd4f969fb557c2177d2f7c08c Mon Sep 17 00:00:00 2001 From: Shaun Eccles Date: Wed, 21 Oct 2026 22:25:24 +1100 Subject: [PATCH 6/6] ci: read the version-scope action's version-only output The plan job read outputs.version_only, but the action names its output version-only, so the value was always empty and nothing was skipped. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7d31997..ef5a673 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 outputs: - version_only: ${{ steps.version_scope.outputs.version_only }} + version_only: ${{ steps.version_scope.outputs.version-only }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: